diff --git a/action.php b/action.php index 191e8eb..9381051 100755 --- a/action.php +++ b/action.php @@ -18,29 +18,96 @@ * Performs actions on the capquiz * * @package mod_capquiz + * @author Sebastian Gundersen * @author Aleksander Skrede - * @copyright 2018 NTNU + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace mod_capquiz; +use mod_capquiz\api; +use mod_capquiz\capquiz; +use mod_capquiz\capquiz_question; +use mod_capquiz\capquiz_question_list; + +require_once ("../../config.php"); + +global $CFG, $DB, $PAGE; -require_once("../../config.php"); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->dirroot . '/mod/capquiz/lib.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/capquiz_action_performer.php'); -$cmid = capquiz_urls::require_course_module_id_param(); +$cmid = required_param('id', PARAM_INT); $cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); +$context = context_module::instance($cmid); require_capability('mod/capquiz:instructor', $context); - $action = required_param('action', PARAM_TEXT); -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlasync); -capquiz_action_performer::perform($action, $capquiz); +$PAGE->set_context($context); +$PAGE->set_cm($cm); +$PAGE->set_url(new moodle_url('/mod/capquiz/action.php')); + +$capquiz = new capquiz($cm->instance); + +switch ($action) { + case 'remove-question': + $questionid = required_param('questionid', PARAM_INT); + $qlist = capquiz_question_list::get_record(['capquiz_id' => $capquiz->get('id')]); + if ($qlist) { + $DB->delete_records('capquiz_question', ['id' => $questionid, 'question_list_id' => $qlist->get('id')]); + } + exit; + + case 'publish-question-list': + $qlist = capquiz_question_list::get_record(['capquiz_id' => $capquiz->get('id')]); + if (capquiz_question::count_records(['question_list_id' => $qlist->get('id')])) { + $capquiz->set('published', 1); + $capquiz->save(); + } + break; + + case 'set-question-list': + $qlistid = required_param('question-list-id', PARAM_INT); + api::copy_question_list($qlistid, $capquiz->get('id')); + break; + + case 'create-question-list-template': + $qlist = capquiz_question_list::get_record(['capquiz_id' => $capquiz->get('id')]); + api::copy_question_list($qlist->get('id'), null); + break; + + case 'merge_qlist': + $sourceqlistid = required_param('qlistid', PARAM_INT); + $qlist = capquiz_question_list::get_record(['capquiz_id' => $capquiz->get('id')]); + foreach (capquiz_question::get_records(['question_list_id' => $sourceqlistid]) as $sourcequestion) { + $existingquestion = capquiz_question::get_record([ + 'question_list_id' => $qlist->get('id'), + 'question_id' => $sourcequestion->get('question_id'), + ]); + if (!$existingquestion) { + $question = new capquiz_question(); + $question->set('question_list_id', $qlist->get('id')); + $question->set('question_id', $sourcequestion->get('question_id')); + $question->save(); + api::set_question_rating($question->get('id'), $sourcequestion->get('rating'), false); + } + } + redirect(new moodle_url('/mod/capquiz/view.php', ['id' => $PAGE->cm->id, 'page' => 'questions'])); + break; + + case 'delete_qlist': + $srcqlistid = required_param('qlistid', PARAM_INT); + $DB->delete_records('capquiz_question', ['question_list_id' => $srcqlistid]); + $DB->delete_records('capquiz_question_list', ['id' => $srcqlistid]); + break; + + case 'regrade-all': + capquiz_update_grades($capquiz->to_record()); + break; + + default: + break; +} -capquiz_urls::redirect_to_dashboard(); +redirect(new moodle_url('/mod/capquiz/view.php', ['id' => $cmid])); diff --git a/adminlib.php b/adminlib.php index 5ce721b..2d5d5d6 100644 --- a/adminlib.php +++ b/adminlib.php @@ -80,7 +80,6 @@ public function search($query): array { } } - /** * Class that handles the display and configuration of the list of capquiz plugins. * diff --git a/adminmanageplugins.php b/adminmanageplugins.php index ca758fd..e3a6802 100644 --- a/adminmanageplugins.php +++ b/adminmanageplugins.php @@ -25,6 +25,8 @@ require_once("../../config.php"); +global $CFG, $PAGE; + require_login(); require_once($CFG->dirroot . '/mod/capquiz/adminlib.php'); diff --git a/amd/build/attempt.min.js b/amd/build/attempt.min.js deleted file mode 100644 index ad1802c..0000000 --- a/amd/build/attempt.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @module mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("mod_capquiz/attempt",["jquery","core/str"],(function($,mString){function showTooltip($element,text){var $tooltip=$(".capquiz-star-tooltip");$tooltip.html(text),$tooltip.css("display","block");var x=$element.offset().left-$tooltip.width()/2,y=$element.offset().top+32;$tooltip.css("left",x+"px"),$tooltip.css("top",y+"px")}function enableTooltips(){$(document).on("mouseover",".capquiz-quiz-stars span",(function(){var $self=$(this);$self.hasClass("capquiz-star")?$.when(mString.get_string("tooltip_achieved_star","capquiz")).done((function(text){showTooltip($self,text)})):$self.hasClass("capquiz-lost-star")?$.when(mString.get_string("tooltip_lost_star","capquiz")).done((function(text){showTooltip($self,text)})):$self.hasClass("capquiz-no-star")?$.when(mString.get_string("tooltip_no_star","capquiz")).done((function(text){showTooltip($self,text)})):$self.hasClass("capquiz-help-stars")&&$.when(mString.get_string("tooltip_help_star","capquiz")).done((function(text){showTooltip($self,text)}))})),$(document).on("mouseleave",".capquiz-quiz-stars span",(function(){$(".capquiz-star-tooltip").css("display","none")}))}return{initialize:function(){enableTooltips()}}})); - -//# sourceMappingURL=attempt.min.js.map \ No newline at end of file diff --git a/amd/build/attempt.min.js.map b/amd/build/attempt.min.js.map deleted file mode 100644 index d7aac5b..0000000 --- a/amd/build/attempt.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"attempt.min.js","sources":["../src/attempt.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 mod_capquiz\n * @author Sebastian S. Gundersen \n * @copyright 2019 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/str'], function($, mString) {\n\n /**\n * Show star tooltip.\n * @param {Object} $element\n * @param {string} text\n */\n function showTooltip($element, text) {\n var $tooltip = $('.capquiz-star-tooltip');\n $tooltip.html(text);\n $tooltip.css('display', 'block');\n var x = $element.offset().left - $tooltip.width() / 2;\n var y = $element.offset().top + 32;\n $tooltip.css('left', x + 'px');\n $tooltip.css('top', y + 'px');\n }\n\n /**\n * Hide star tooltip.\n */\n function hideTooltip() {\n $('.capquiz-star-tooltip').css('display', 'none');\n }\n\n /**\n * Register event listeners for showing tooltips on the stars.\n */\n function enableTooltips() {\n $(document).on('mouseover', '.capquiz-quiz-stars span', function() {\n var $self = $(this);\n if ($self.hasClass('capquiz-star')) {\n $.when(mString.get_string('tooltip_achieved_star', 'capquiz')).done(function(text) {\n showTooltip($self, text);\n });\n } else if ($self.hasClass('capquiz-lost-star')) {\n $.when(mString.get_string('tooltip_lost_star', 'capquiz')).done(function(text) {\n showTooltip($self, text);\n });\n } else if ($self.hasClass('capquiz-no-star')) {\n $.when(mString.get_string('tooltip_no_star', 'capquiz')).done(function(text) {\n showTooltip($self, text);\n });\n } else if ($self.hasClass('capquiz-help-stars')) {\n $.when(mString.get_string('tooltip_help_star', 'capquiz')).done(function(text) {\n showTooltip($self, text);\n });\n }\n });\n $(document).on('mouseleave', '.capquiz-quiz-stars span', function() {\n hideTooltip();\n });\n }\n\n return {\n initialize: function() {\n enableTooltips();\n }\n };\n\n});\n"],"names":["define","$","mString","showTooltip","$element","text","$tooltip","html","css","x","offset","left","width","y","top","enableTooltips","document","on","$self","this","hasClass","when","get_string","done","initialize"],"mappings":";;;;;;AAsBAA,6BAAO,CAAC,SAAU,aAAa,SAASC,EAAGC,kBAO9BC,YAAYC,SAAUC,UACvBC,SAAWL,EAAE,yBACjBK,SAASC,KAAKF,MACdC,SAASE,IAAI,UAAW,aACpBC,EAAIL,SAASM,SAASC,KAAOL,SAASM,QAAU,EAChDC,EAAIT,SAASM,SAASI,IAAM,GAChCR,SAASE,IAAI,OAAQC,EAAI,MACzBH,SAASE,IAAI,MAAOK,EAAI,eAanBE,iBACLd,EAAEe,UAAUC,GAAG,YAAa,4BAA4B,eAChDC,MAAQjB,EAAEkB,MACVD,MAAME,SAAS,gBACfnB,EAAEoB,KAAKnB,QAAQoB,WAAW,wBAAyB,YAAYC,MAAK,SAASlB,MACzEF,YAAYe,MAAOb,SAEhBa,MAAME,SAAS,qBACtBnB,EAAEoB,KAAKnB,QAAQoB,WAAW,oBAAqB,YAAYC,MAAK,SAASlB,MACrEF,YAAYe,MAAOb,SAEhBa,MAAME,SAAS,mBACtBnB,EAAEoB,KAAKnB,QAAQoB,WAAW,kBAAmB,YAAYC,MAAK,SAASlB,MACnEF,YAAYe,MAAOb,SAEhBa,MAAME,SAAS,uBACtBnB,EAAEoB,KAAKnB,QAAQoB,WAAW,oBAAqB,YAAYC,MAAK,SAASlB,MACrEF,YAAYe,MAAOb,YAI/BJ,EAAEe,UAAUC,GAAG,aAAc,4BAA4B,WA3BzDhB,EAAE,yBAAyBO,IAAI,UAAW,iBAgCvC,CACHgB,WAAY,WACRT"} \ No newline at end of file diff --git a/amd/build/edit_questions.min.js b/amd/build/edit_questions.min.js index e709700..8eb072d 100755 --- a/amd/build/edit_questions.min.js +++ b/amd/build/edit_questions.min.js @@ -1,9 +1,3 @@ -/** - * @module mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("mod_capquiz/edit_questions",["jquery"],(function($){var parameters={courseModuleId:0};function sendAction(data,onSuccess,onError){$.ajax({type:"post",url:"action.php",data:data,success:onSuccess,error:onError})}function sendDefaultQuestionRating(data,rating,onSuccess,onError){sendAction({action:"set-default-question-rating",id:parameters.courseModuleId,rating:rating},onSuccess,onError)}function sendQuestionRating(data,rating,onSuccess,onError){sendAction({action:"set-question-rating",id:parameters.courseModuleId,"question-id":data.questionId,rating:rating},onSuccess,onError)}function submitInput($input,sendInput,data){$input.data("saving",!0),$input.data("dirty",!1);var $indicator=$input.next();$indicator.css("color","blue"),sendInput(data,$input.val(),(function(){!0===$input.data("dirty")?submitInput($input,sendInput,data):($indicator.css("color","green"),$input.data("dirty",!1),$input.data("saving",!1))}),(function(){$indicator.css("color","red")}))}function submitQuestionRating($input){submitInput($input,sendQuestionRating,{questionId:$input.data("question-id")})}function submitDefaultQuestionRating($input){submitInput($input,sendDefaultQuestionRating,null)}function registerListener(query,submit){$(document).on("input",query,(function(event){var $input=$(event.target);!0!==$input.data("saving")?submit($input):$input.data("dirty",!0)}))}function sortTable($header){const column=$header.index(),$table=$header.parent().parent();let $rows=$table.find("tr:gt(0)").toArray().sort((function(rowA,rowB){const $colA=$(rowA).children("td").eq(0),$colB=$(rowB).children("td").eq(0);return parseInt($colA.text())-parseInt($colB.text())}));$table.append($rows),$rows=$table.find("tr:gt(0)").toArray().sort((function(rowA,rowB){const $colA=$(rowA).children("td").eq(column),$colB=$(rowB).children("td").eq(column),$itemA=$colA.find(".capquiz-sortable-item"),$itemB=$colB.find(".capquiz-sortable-item");let valA,valB;return valA=0===$itemA.length?$colA.html():0===$itemA.val().length?$itemA.html():$itemA.val(),valB=0===$itemB.length?$colB.html():0===$itemB.val().length?$itemB.html():$itemB.val(),$.isNumeric(valA)&&$.isNumeric(valB)?valA-valB:valA.toString().localeCompare(valB)}));const ascending="true"===$table.data("asc");$table.data("asc",ascending?"false":"true");const iconName=ascending?"fa-arrow-up":"fa-arrow-down";$.each($table.find(".capquiz-sortable"),(function(){$(this).find(".fa").remove()})),$header.prepend(''),ascending||($rows=$rows.reverse()),$table.append($rows);let i=1;$table.find("tr:gt(0)").each((function(){$(this).find("td:first-child").html(i),i++}))}return{initialize:function(courseModuleId){parameters.courseModuleId=courseModuleId,registerListener(".capquiz-question-rating input",submitQuestionRating),registerListener(".capquiz-default-question-rating input",submitDefaultQuestionRating),$(".capquiz-question-rating-submit-wrapper button").each((function(index,object){$(object).attr("tabindex",-1)})),$(document).on("click",".capquiz-sortable",(function(){sortTable($(this))})),$(".capquiz-sortable-default-asc").each((function(){sortTable($(this)),sortTable($(this))})),$(".capquiz-sortable-default-desc").each((function(){sortTable($(this))})),$(".capquiz-add-selected-questions").on("click",(function(){var questionIds="";$("#categoryquestions td input[type=checkbox]:checked").each((function(){questionIds+=$(this).attr("name").slice(1)+","})),$.post("action.php",{action:"add-question",id:parameters.courseModuleId,"question-id":questionIds},(function(){location.reload()}))}))}}})); +define("mod_capquiz/edit_questions",["exports","core/ajax"],(function(_exports,_ajax){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initialize=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};const onEditQuestionRating=async input=>{if("true"===input.dataset.saving)return void(input.dataset.dirty="true");input.dataset.saving="true",input.dataset.dirty="false";const indicator=input.nextElementSibling;indicator.style.color="blue";try{if(await(questionId=parseInt(input.dataset.questionId),rating=parseFloat(input.value),_ajax.default.call({methodname:"mod_capquiz_set_question_rating",args:{questionid:questionId,rating:rating}})[0]),"true"===input.dataset.dirty)return onEditQuestionRating(input);indicator.style.color="green",input.dataset.dirty="false",input.dataset.saving="false"}catch(error){indicator.style.color="red"}var questionId,rating};_exports.initialize=courseModuleId=>{document.addEventListener("input",(async event=>{const input=event.target.closest(".capquiz-question-rating input");input&&await onEditQuestionRating(input)})),document.addEventListener("click",(async event=>{event.target.closest(".capquiz-add-selected-questions")&&await _ajax.default.call({methodname:"mod_capquiz_add_questions",args:{cmid:courseModuleId,questionids:[...document.querySelectorAll("#categoryquestions td input[type=checkbox]:checked")].map((checkbox=>checkbox.getAttribute("name").slice(1))).join(",")}})[0]}))}})); //# sourceMappingURL=edit_questions.min.js.map \ No newline at end of file diff --git a/amd/build/edit_questions.min.js.map b/amd/build/edit_questions.min.js.map index a7b839c..8890ede 100644 --- a/amd/build/edit_questions.min.js.map +++ b/amd/build/edit_questions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"edit_questions.min.js","sources":["../src/edit_questions.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 mod_capquiz\n * @author Sebastian S. Gundersen \n * @copyright 2019 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery'], function($) {\n\n var parameters = {\n courseModuleId: 0,\n };\n\n /**\n * Send an action to the server.\n * @param {Object} data\n * @param {Object} onSuccess\n * @param {Object} onError\n */\n function sendAction(data, onSuccess, onError) {\n $.ajax({\n type: 'post',\n url: 'action.php',\n data: data,\n success: onSuccess,\n error: onError\n });\n }\n\n /**\n * Send the new default rating for the question list to the server.\n * @param {Object} data\n * @param {number} rating\n * @param {Object} onSuccess\n * @param {Object} onError\n */\n function sendDefaultQuestionRating(data, rating, onSuccess, onError) {\n sendAction({\n 'action': 'set-default-question-rating',\n 'id': parameters.courseModuleId,\n 'rating': rating,\n }, onSuccess, onError);\n }\n\n /**\n * Send the new rating for the question to the server.\n * @param {Object} data\n * @param {number} rating\n * @param {Object} onSuccess\n * @param {Object} onError\n */\n function sendQuestionRating(data, rating, onSuccess, onError) {\n sendAction({\n 'action': 'set-question-rating',\n 'id': parameters.courseModuleId,\n 'question-id': data.questionId,\n 'rating': rating,\n }, onSuccess, onError);\n }\n\n /**\n * Send the new value, and avoid race condition.\n * @param {Object} $input\n * @param {Object} sendInput\n * @param {Object} data\n */\n function submitInput($input, sendInput, data) {\n $input.data('saving', true);\n $input.data('dirty', false);\n var $indicator = $input.next();\n $indicator.css('color', 'blue');\n sendInput(data, $input.val(), function() {\n if ($input.data('dirty') === true) {\n submitInput($input, sendInput, data);\n } else {\n $indicator.css('color', 'green');\n $input.data('dirty', false);\n $input.data('saving', false);\n }\n }, function() {\n $indicator.css('color', 'red');\n });\n }\n\n /**\n * Send the new rating for the question, and avoid race condition.\n * @param {Object} $input\n */\n function submitQuestionRating($input) {\n submitInput($input, sendQuestionRating, {questionId: $input.data('question-id')});\n }\n\n /**\n * Send the new default rating for the question list, and avoid race condition.\n * @param {Object} $input\n */\n function submitDefaultQuestionRating($input) {\n submitInput($input, sendDefaultQuestionRating, null);\n }\n\n /**\n * Register an input event listener for submission.\n * @param {string} query\n * @param {Object} submit\n */\n function registerListener(query, submit) {\n $(document).on('input', query, function(event) {\n var $input = $(event.target);\n var isBeingSaved = $input.data('saving');\n if (isBeingSaved === true) {\n $input.data('dirty', true);\n return;\n }\n submit($input);\n });\n }\n\n /**\n * Sorts a table by the respective column based on $header.\n * It searches for an element of class \"capquiz-sortable-item\" inside the , and if found,\n * the value attribute is used if it exists. Otherwise, the inner html is used to sort by.\n *\n * The tag may not have the item class, as it has no effect on the sorting.\n * Their children elements are not required to have the class either. The inner html of will be used then.\n *\n * The first column in the table must be an index of the row.\n *\n * @param {Object} $header The header column for which to sort the table by.\n */\n function sortTable($header) {\n const column = $header.index();\n const $table = $header.parent().parent();\n let $rows = $table.find('tr:gt(0)').toArray().sort(function(rowA, rowB) {\n const $colA = $(rowA).children('td').eq(0);\n const $colB = $(rowB).children('td').eq(0);\n return parseInt($colA.text()) - parseInt($colB.text());\n });\n $table.append($rows);\n $rows = $table.find('tr:gt(0)').toArray().sort(function(rowA, rowB) {\n const $colA = $(rowA).children('td').eq(column);\n const $colB = $(rowB).children('td').eq(column);\n const $itemA = $colA.find('.capquiz-sortable-item');\n const $itemB = $colB.find('.capquiz-sortable-item');\n let valA;\n if ($itemA.length === 0) {\n valA = $colA.html();\n } else {\n valA = $itemA.val().length === 0 ? $itemA.html() : $itemA.val();\n }\n let valB;\n if ($itemB.length === 0) {\n valB = $colB.html();\n } else {\n valB = $itemB.val().length === 0 ? $itemB.html() : $itemB.val();\n }\n if ($.isNumeric(valA) && $.isNumeric(valB)) {\n return valA - valB;\n } else {\n return valA.toString().localeCompare(valB);\n }\n });\n const ascending = ($table.data('asc') === 'true');\n $table.data('asc', ascending ? 'false' : 'true');\n const iconName = (ascending ? 'fa-arrow-up' : 'fa-arrow-down');\n $.each($table.find('.capquiz-sortable'), function() {\n $(this).find('.fa').remove();\n });\n $header.prepend('');\n if (!ascending) {\n $rows = $rows.reverse();\n }\n $table.append($rows);\n let i = 1;\n $table.find('tr:gt(0)').each(function() {\n $(this).find('td:first-child').html(i);\n i++;\n });\n }\n\n /**\n * Register click event listeners for the sortable table columns.\n */\n function registerSortListener() {\n $(document).on('click', '.capquiz-sortable', function() {\n sortTable($(this));\n });\n $('.capquiz-sortable-default-asc').each(function() {\n sortTable($(this));\n sortTable($(this));\n });\n $('.capquiz-sortable-default-desc').each(function() {\n sortTable($(this));\n });\n }\n\n /**\n * Set the tab indices for the question rating elements to be more user friendly.\n */\n function fixTabIndicesForQuestionRatingInputs() {\n $('.capquiz-question-rating-submit-wrapper button').each(function(index, object) {\n $(object).attr('tabindex', -1);\n });\n }\n\n /**\n * Register click event listener for \"Add to quiz\" button.\n */\n function listenAddToQuiz() {\n $('.capquiz-add-selected-questions').on('click', function() {\n var questionIds = '';\n $('#categoryquestions td input[type=checkbox]:checked').each(function() {\n questionIds += $(this).attr('name').slice(1) + ',';\n });\n $.post('action.php', {\n 'action': 'add-question',\n 'id': parameters.courseModuleId,\n 'question-id': questionIds,\n }, function() {\n location.reload();\n });\n });\n }\n\n return {\n initialize: function(courseModuleId) {\n parameters.courseModuleId = courseModuleId;\n registerListener('.capquiz-question-rating input', submitQuestionRating);\n registerListener('.capquiz-default-question-rating input', submitDefaultQuestionRating);\n fixTabIndicesForQuestionRatingInputs();\n registerSortListener();\n listenAddToQuiz();\n }\n };\n\n});\n"],"names":["define","$","parameters","courseModuleId","sendAction","data","onSuccess","onError","ajax","type","url","success","error","sendDefaultQuestionRating","rating","sendQuestionRating","questionId","submitInput","$input","sendInput","$indicator","next","css","val","submitQuestionRating","submitDefaultQuestionRating","registerListener","query","submit","document","on","event","target","sortTable","$header","column","index","$table","parent","$rows","find","toArray","sort","rowA","rowB","$colA","children","eq","$colB","parseInt","text","append","$itemA","$itemB","valA","valB","length","html","isNumeric","toString","localeCompare","ascending","iconName","each","this","remove","prepend","reverse","i","initialize","object","attr","questionIds","slice","post","location","reload"],"mappings":";;;;;;AAsBAA,oCAAO,CAAC,WAAW,SAASC,OAEpBC,WAAa,CACbC,eAAgB,YASXC,WAAWC,KAAMC,UAAWC,SACjCN,EAAEO,KAAK,CACHC,KAAM,OACNC,IAAK,aACLL,KAAMA,KACNM,QAASL,UACTM,MAAOL,mBAWNM,0BAA0BR,KAAMS,OAAQR,UAAWC,SACxDH,WAAW,QACG,iCACJF,WAAWC,sBACPW,QACXR,UAAWC,kBAUTQ,mBAAmBV,KAAMS,OAAQR,UAAWC,SACjDH,WAAW,QACG,yBACJF,WAAWC,6BACFE,KAAKW,kBACVF,QACXR,UAAWC,kBASTU,YAAYC,OAAQC,UAAWd,MACpCa,OAAOb,KAAK,UAAU,GACtBa,OAAOb,KAAK,SAAS,OACjBe,WAAaF,OAAOG,OACxBD,WAAWE,IAAI,QAAS,QACxBH,UAAUd,KAAMa,OAAOK,OAAO,YACG,IAAzBL,OAAOb,KAAK,SACZY,YAAYC,OAAQC,UAAWd,OAE/Be,WAAWE,IAAI,QAAS,SACxBJ,OAAOb,KAAK,SAAS,GACrBa,OAAOb,KAAK,UAAU,OAE3B,WACCe,WAAWE,IAAI,QAAS,mBAQvBE,qBAAqBN,QAC1BD,YAAYC,OAAQH,mBAAoB,CAACC,WAAYE,OAAOb,KAAK,0BAO5DoB,4BAA4BP,QACjCD,YAAYC,OAAQL,0BAA2B,eAQ1Ca,iBAAiBC,MAAOC,QAC7B3B,EAAE4B,UAAUC,GAAG,QAASH,OAAO,SAASI,WAChCb,OAASjB,EAAE8B,MAAMC,SAEA,IADFd,OAAOb,KAAK,UAK/BuB,OAAOV,QAHHA,OAAOb,KAAK,SAAS,eAmBxB4B,UAAUC,eACTC,OAASD,QAAQE,QACjBC,OAASH,QAAQI,SAASA,aAC5BC,MAAQF,OAAOG,KAAK,YAAYC,UAAUC,MAAK,SAASC,KAAMC,YACxDC,MAAQ5C,EAAE0C,MAAMG,SAAS,MAAMC,GAAG,GAClCC,MAAQ/C,EAAE2C,MAAME,SAAS,MAAMC,GAAG,UACjCE,SAASJ,MAAMK,QAAUD,SAASD,MAAME,WAEnDb,OAAOc,OAAOZ,OACdA,MAAQF,OAAOG,KAAK,YAAYC,UAAUC,MAAK,SAASC,KAAMC,YACpDC,MAAQ5C,EAAE0C,MAAMG,SAAS,MAAMC,GAAGZ,QAClCa,MAAQ/C,EAAE2C,MAAME,SAAS,MAAMC,GAAGZ,QAClCiB,OAASP,MAAML,KAAK,0BACpBa,OAASL,MAAMR,KAAK,8BACtBc,KAMAC,YAJAD,KADkB,IAAlBF,OAAOI,OACAX,MAAMY,OAEkB,IAAxBL,OAAO7B,MAAMiC,OAAeJ,OAAOK,OAASL,OAAO7B,MAI1DgC,KADkB,IAAlBF,OAAOG,OACAR,MAAMS,OAEkB,IAAxBJ,OAAO9B,MAAMiC,OAAeH,OAAOI,OAASJ,OAAO9B,MAE1DtB,EAAEyD,UAAUJ,OAASrD,EAAEyD,UAAUH,MAC1BD,KAAOC,KAEPD,KAAKK,WAAWC,cAAcL,eAGvCM,UAAoC,SAAvBxB,OAAOhC,KAAK,OAC/BgC,OAAOhC,KAAK,MAAOwD,UAAY,QAAU,cACnCC,SAAYD,UAAY,cAAgB,gBAC9C5D,EAAE8D,KAAK1B,OAAOG,KAAK,sBAAsB,WACrCvC,EAAE+D,MAAMxB,KAAK,OAAOyB,YAExB/B,QAAQgC,QAAQ,gBAAkBJ,SAAW,UACxCD,YACDtB,MAAQA,MAAM4B,WAElB9B,OAAOc,OAAOZ,WACV6B,EAAI,EACR/B,OAAOG,KAAK,YAAYuB,MAAK,WACzB9D,EAAE+D,MAAMxB,KAAK,kBAAkBiB,KAAKW,GACpCA,aAgDD,CACHC,WAAY,SAASlE,gBACjBD,WAAWC,eAAiBA,eAC5BuB,iBAAiB,iCAAkCF,sBACnDE,iBAAiB,yCAA0CD,6BA5B/DxB,EAAE,kDAAkD8D,MAAK,SAAS3B,MAAOkC,QACrErE,EAAEqE,QAAQC,KAAK,YAAa,MAjBhCtE,EAAE4B,UAAUC,GAAG,QAAS,qBAAqB,WACzCG,UAAUhC,EAAE+D,UAEhB/D,EAAE,iCAAiC8D,MAAK,WACpC9B,UAAUhC,EAAE+D,OACZ/B,UAAUhC,EAAE+D,UAEhB/D,EAAE,kCAAkC8D,MAAK,WACrC9B,UAAUhC,EAAE+D,UAiBhB/D,EAAE,mCAAmC6B,GAAG,SAAS,eACzC0C,YAAc,GAClBvE,EAAE,sDAAsD8D,MAAK,WACzDS,aAAevE,EAAE+D,MAAMO,KAAK,QAAQE,MAAM,GAAK,OAEnDxE,EAAEyE,KAAK,aAAc,QACP,kBACJxE,WAAWC,6BACFqE,cAChB,WACCG,SAASC"} \ No newline at end of file +{"version":3,"file":"edit_questions.min.js","sources":["../src/edit_questions.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 mod_capquiz\n * @author Sebastian Gundersen \n * @copyright 2024 Norwegian University of Science and Technology (NTNU)\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport Ajax from 'core/ajax';\n\n/**\n * Set question rating.\n *\n * @param {Number} questionId\n * @param {Number} rating\n * @returns {Promise}\n */\nconst setQuestionRating = (questionId, rating) => {\n return Ajax.call({\n methodname:'mod_capquiz_set_question_rating',\n args: {questionid: questionId, rating: rating},\n })[0];\n};\n\n/**\n * Handle update of question rating.\n *\n * @param {HTMLInputElement} input\n */\nconst onEditQuestionRating = async input => {\n if (input.dataset.saving === 'true') {\n input.dataset.dirty = 'true';\n return;\n }\n input.dataset.saving = 'true';\n input.dataset.dirty = 'false';\n const indicator = input.nextElementSibling;\n indicator.style.color = 'blue';\n try {\n await setQuestionRating(parseInt(input.dataset.questionId), parseFloat(input.value));\n if (input.dataset.dirty === 'true') {\n return onEditQuestionRating(input);\n } else {\n indicator.style.color = 'green';\n input.dataset.dirty = 'false';\n input.dataset.saving = 'false';\n }\n } catch (error) {\n indicator.style.color = 'red';\n }\n};\n\n/**\n * Register event listeners.\n *\n * @param {Number} courseModuleId\n */\nexport const initialize = courseModuleId => {\n document.addEventListener('input', async event => {\n const input = event.target.closest('.capquiz-question-rating input');\n if (input) {\n await onEditQuestionRating(input);\n }\n });\n\n document.addEventListener('click', async event => {\n if (event.target.closest('.capquiz-add-selected-questions')) {\n await Ajax.call({\n methodname:'mod_capquiz_add_questions',\n args: {\n cmid: courseModuleId,\n questionids: [...document.querySelectorAll('#categoryquestions td input[type=checkbox]:checked')]\n .map(checkbox => checkbox.getAttribute('name').slice(1))\n .join(','),\n },\n })[0];\n }\n });\n};\n\n"],"names":["onEditQuestionRating","async","input","dataset","saving","dirty","indicator","nextElementSibling","style","color","questionId","parseInt","rating","parseFloat","value","Ajax","call","methodname","args","questionid","error","courseModuleId","document","addEventListener","event","target","closest","cmid","questionids","querySelectorAll","map","checkbox","getAttribute","slice","join"],"mappings":"2OA6CMA,qBAAuBC,MAAAA,WACI,SAAzBC,MAAMC,QAAQC,mBACdF,MAAMC,QAAQE,MAAQ,QAG1BH,MAAMC,QAAQC,OAAS,OACvBF,MAAMC,QAAQE,MAAQ,cAChBC,UAAYJ,MAAMK,mBACxBD,UAAUE,MAAMC,MAAQ,oBApBDC,WAsBKC,SAAST,MAAMC,QAAQO,YAtBhBE,OAsB6BC,WAAWX,MAAMY,OArB1EC,cAAKC,KAAK,CACbC,WAAW,kCACXC,KAAM,CAACC,WAAYT,WAAYE,OAAQA,UACxC,IAmB6B,SAAxBV,MAAMC,QAAQE,aACPL,qBAAqBE,OAE5BI,UAAUE,MAAMC,MAAQ,QACxBP,MAAMC,QAAQE,MAAQ,QACtBH,MAAMC,QAAQC,OAAS,QAE7B,MAAOgB,OACLd,UAAUE,MAAMC,MAAQ,MA/BN,IAACC,WAAYE,4BAwCbS,iBACtBC,SAASC,iBAAiB,SAAStB,MAAAA,cACzBC,MAAQsB,MAAMC,OAAOC,QAAQ,kCAC/BxB,aACMF,qBAAqBE,UAInCoB,SAASC,iBAAiB,SAAStB,MAAAA,QAC3BuB,MAAMC,OAAOC,QAAQ,0CACfX,cAAKC,KAAK,CACZC,WAAW,4BACXC,KAAM,CACFS,KAAMN,eACNO,YAAa,IAAIN,SAASO,iBAAiB,uDACtCC,KAAIC,UAAYA,SAASC,aAAa,QAAQC,MAAM,KACpDC,KAAK,QAEf"} \ No newline at end of file diff --git a/amd/src/attempt.js b/amd/src/attempt.js deleted file mode 100644 index 0588066..0000000 --- a/amd/src/attempt.js +++ /dev/null @@ -1,82 +0,0 @@ -// 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 . - -/** - * @module mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define(['jquery', 'core/str'], function($, mString) { - - /** - * Show star tooltip. - * @param {Object} $element - * @param {string} text - */ - function showTooltip($element, text) { - var $tooltip = $('.capquiz-star-tooltip'); - $tooltip.html(text); - $tooltip.css('display', 'block'); - var x = $element.offset().left - $tooltip.width() / 2; - var y = $element.offset().top + 32; - $tooltip.css('left', x + 'px'); - $tooltip.css('top', y + 'px'); - } - - /** - * Hide star tooltip. - */ - function hideTooltip() { - $('.capquiz-star-tooltip').css('display', 'none'); - } - - /** - * Register event listeners for showing tooltips on the stars. - */ - function enableTooltips() { - $(document).on('mouseover', '.capquiz-quiz-stars span', function() { - var $self = $(this); - if ($self.hasClass('capquiz-star')) { - $.when(mString.get_string('tooltip_achieved_star', 'capquiz')).done(function(text) { - showTooltip($self, text); - }); - } else if ($self.hasClass('capquiz-lost-star')) { - $.when(mString.get_string('tooltip_lost_star', 'capquiz')).done(function(text) { - showTooltip($self, text); - }); - } else if ($self.hasClass('capquiz-no-star')) { - $.when(mString.get_string('tooltip_no_star', 'capquiz')).done(function(text) { - showTooltip($self, text); - }); - } else if ($self.hasClass('capquiz-help-stars')) { - $.when(mString.get_string('tooltip_help_star', 'capquiz')).done(function(text) { - showTooltip($self, text); - }); - } - }); - $(document).on('mouseleave', '.capquiz-quiz-stars span', function() { - hideTooltip(); - }); - } - - return { - initialize: function() { - enableTooltips(); - } - }; - -}); diff --git a/amd/src/edit_questions.js b/amd/src/edit_questions.js index a2984f4..7edf374 100755 --- a/amd/src/edit_questions.js +++ b/amd/src/edit_questions.js @@ -15,236 +15,82 @@ /** * @module mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery'], function($) { +"use strict"; - var parameters = { - courseModuleId: 0, - }; +import Ajax from 'core/ajax'; - /** - * Send an action to the server. - * @param {Object} data - * @param {Object} onSuccess - * @param {Object} onError - */ - function sendAction(data, onSuccess, onError) { - $.ajax({ - type: 'post', - url: 'action.php', - data: data, - success: onSuccess, - error: onError - }); - } - - /** - * Send the new default rating for the question list to the server. - * @param {Object} data - * @param {number} rating - * @param {Object} onSuccess - * @param {Object} onError - */ - function sendDefaultQuestionRating(data, rating, onSuccess, onError) { - sendAction({ - 'action': 'set-default-question-rating', - 'id': parameters.courseModuleId, - 'rating': rating, - }, onSuccess, onError); - } - - /** - * Send the new rating for the question to the server. - * @param {Object} data - * @param {number} rating - * @param {Object} onSuccess - * @param {Object} onError - */ - function sendQuestionRating(data, rating, onSuccess, onError) { - sendAction({ - 'action': 'set-question-rating', - 'id': parameters.courseModuleId, - 'question-id': data.questionId, - 'rating': rating, - }, onSuccess, onError); - } - - /** - * Send the new value, and avoid race condition. - * @param {Object} $input - * @param {Object} sendInput - * @param {Object} data - */ - function submitInput($input, sendInput, data) { - $input.data('saving', true); - $input.data('dirty', false); - var $indicator = $input.next(); - $indicator.css('color', 'blue'); - sendInput(data, $input.val(), function() { - if ($input.data('dirty') === true) { - submitInput($input, sendInput, data); - } else { - $indicator.css('color', 'green'); - $input.data('dirty', false); - $input.data('saving', false); - } - }, function() { - $indicator.css('color', 'red'); - }); - } - - /** - * Send the new rating for the question, and avoid race condition. - * @param {Object} $input - */ - function submitQuestionRating($input) { - submitInput($input, sendQuestionRating, {questionId: $input.data('question-id')}); - } - - /** - * Send the new default rating for the question list, and avoid race condition. - * @param {Object} $input - */ - function submitDefaultQuestionRating($input) { - submitInput($input, sendDefaultQuestionRating, null); - } +/** + * Set question rating. + * + * @param {Number} questionId + * @param {Number} rating + * @returns {Promise} + */ +const setQuestionRating = (questionId, rating) => { + return Ajax.call({ + methodname:'mod_capquiz_set_question_rating', + args: {questionid: questionId, rating: rating}, + })[0]; +}; - /** - * Register an input event listener for submission. - * @param {string} query - * @param {Object} submit - */ - function registerListener(query, submit) { - $(document).on('input', query, function(event) { - var $input = $(event.target); - var isBeingSaved = $input.data('saving'); - if (isBeingSaved === true) { - $input.data('dirty', true); - return; - } - submit($input); - }); +/** + * Handle update of question rating. + * + * @param {HTMLInputElement} input + */ +const onEditQuestionRating = async input => { + if (input.dataset.saving === 'true') { + input.dataset.dirty = 'true'; + return; } - - /** - * Sorts a table by the respective column based on $header. - * It searches for an element of class "capquiz-sortable-item" inside the , and if found, - * the value attribute is used if it exists. Otherwise, the inner html is used to sort by. - * - * The tag may not have the item class, as it has no effect on the sorting. - * Their children elements are not required to have the class either. The inner html of will be used then. - * - * The first column in the table must be an index of the row. - * - * @param {Object} $header The header column for which to sort the table by. - */ - function sortTable($header) { - const column = $header.index(); - const $table = $header.parent().parent(); - let $rows = $table.find('tr:gt(0)').toArray().sort(function(rowA, rowB) { - const $colA = $(rowA).children('td').eq(0); - const $colB = $(rowB).children('td').eq(0); - return parseInt($colA.text()) - parseInt($colB.text()); - }); - $table.append($rows); - $rows = $table.find('tr:gt(0)').toArray().sort(function(rowA, rowB) { - const $colA = $(rowA).children('td').eq(column); - const $colB = $(rowB).children('td').eq(column); - const $itemA = $colA.find('.capquiz-sortable-item'); - const $itemB = $colB.find('.capquiz-sortable-item'); - let valA; - if ($itemA.length === 0) { - valA = $colA.html(); - } else { - valA = $itemA.val().length === 0 ? $itemA.html() : $itemA.val(); - } - let valB; - if ($itemB.length === 0) { - valB = $colB.html(); - } else { - valB = $itemB.val().length === 0 ? $itemB.html() : $itemB.val(); - } - if ($.isNumeric(valA) && $.isNumeric(valB)) { - return valA - valB; - } else { - return valA.toString().localeCompare(valB); - } - }); - const ascending = ($table.data('asc') === 'true'); - $table.data('asc', ascending ? 'false' : 'true'); - const iconName = (ascending ? 'fa-arrow-up' : 'fa-arrow-down'); - $.each($table.find('.capquiz-sortable'), function() { - $(this).find('.fa').remove(); - }); - $header.prepend(''); - if (!ascending) { - $rows = $rows.reverse(); + input.dataset.saving = 'true'; + input.dataset.dirty = 'false'; + const indicator = input.nextElementSibling; + indicator.style.color = 'blue'; + try { + await setQuestionRating(parseInt(input.dataset.questionId), parseFloat(input.value)); + if (input.dataset.dirty === 'true') { + return onEditQuestionRating(input); + } else { + indicator.style.color = 'green'; + input.dataset.dirty = 'false'; + input.dataset.saving = 'false'; } - $table.append($rows); - let i = 1; - $table.find('tr:gt(0)').each(function() { - $(this).find('td:first-child').html(i); - i++; - }); - } - - /** - * Register click event listeners for the sortable table columns. - */ - function registerSortListener() { - $(document).on('click', '.capquiz-sortable', function() { - sortTable($(this)); - }); - $('.capquiz-sortable-default-asc').each(function() { - sortTable($(this)); - sortTable($(this)); - }); - $('.capquiz-sortable-default-desc').each(function() { - sortTable($(this)); - }); - } - - /** - * Set the tab indices for the question rating elements to be more user friendly. - */ - function fixTabIndicesForQuestionRatingInputs() { - $('.capquiz-question-rating-submit-wrapper button').each(function(index, object) { - $(object).attr('tabindex', -1); - }); - } - - /** - * Register click event listener for "Add to quiz" button. - */ - function listenAddToQuiz() { - $('.capquiz-add-selected-questions').on('click', function() { - var questionIds = ''; - $('#categoryquestions td input[type=checkbox]:checked').each(function() { - questionIds += $(this).attr('name').slice(1) + ','; - }); - $.post('action.php', { - 'action': 'add-question', - 'id': parameters.courseModuleId, - 'question-id': questionIds, - }, function() { - location.reload(); - }); - }); + } catch (error) { + indicator.style.color = 'red'; } +}; - return { - initialize: function(courseModuleId) { - parameters.courseModuleId = courseModuleId; - registerListener('.capquiz-question-rating input', submitQuestionRating); - registerListener('.capquiz-default-question-rating input', submitDefaultQuestionRating); - fixTabIndicesForQuestionRatingInputs(); - registerSortListener(); - listenAddToQuiz(); +/** + * Register event listeners. + * + * @param {Number} courseModuleId + */ +export const initialize = courseModuleId => { + document.addEventListener('input', async event => { + const input = event.target.closest('.capquiz-question-rating input'); + if (input) { + await onEditQuestionRating(input); + } + }); + + document.addEventListener('click', async event => { + if (event.target.closest('.capquiz-add-selected-questions')) { + await Ajax.call({ + methodname:'mod_capquiz_add_questions', + args: { + cmid: courseModuleId, + questionids: [...document.querySelectorAll('#categoryquestions td input[type=checkbox]:checked')] + .map(checkbox => checkbox.getAttribute('name').slice(1)) + .join(','), + }, + })[0]; } - }; + }); +}; -}); diff --git a/async.php b/async.php index 7a9ed25..7a75167 100755 --- a/async.php +++ b/async.php @@ -19,36 +19,42 @@ * * @package mod_capquiz * @author Aleksander Skrede - * @copyright 2018 NTNU + * @copyright 2018 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace mod_capquiz; +use mod_capquiz\api; +use mod_capquiz\capquiz; +use mod_capquiz\capquiz_question_attempt; -require_once('../../config.php'); +require_once(__DIR__ . '/../../config.php'); -$cmid = capquiz_urls::require_course_module_id_param(); +global $PAGE, $USER; + +$cmid = required_param('id', PARAM_INT); $cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); + +$context = context_module::instance($cmid); require_capability('mod/capquiz:student', $context); +$attemptid = required_param('attempt', PARAM_INT); $action = required_param('action', PARAM_TEXT); -$attemptid = optional_param('attempt', null, PARAM_INT); -$cmid = capquiz_urls::require_course_module_id_param(); -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlasync); +$PAGE->set_context($context); +$PAGE->set_cm($cm); -if ($attemptid !== null) { - $user = $capquiz->user(); - $attempt = capquiz_question_attempt::load_attempt($user, $attemptid); +$capquiz = new capquiz($cm->instance); +$user = api::get_user($capquiz, $USER->id); +$attempt = new capquiz_question_attempt($attemptid); +if ($attempt->get('user_id') === $user->get('id')) { if ($action === 'answered') { - $capquiz->question_engine($user)->attempt_answered($user, $attempt); + api::attempt_answered($capquiz, $user, $attempt); } else if ($action === 'reviewed') { - $capquiz->question_engine($user)->attempt_reviewed($attempt); + $attempt->set('reviewed', 1); + $attempt->set('time_reviewed', time()); + $attempt->save(); } - capquiz_urls::redirect_to_dashboard(); } -capquiz_urls::redirect_to_front_page(); +redirect(new moodle_url('/mod/capquiz/view.php', ['id' => $cmid])); diff --git a/backup/moodle2/backup_capquiz_activity_task.class.php b/backup/moodle2/backup_capquiz_activity_task.class.php index 7319225..556fec3 100644 --- a/backup/moodle2/backup_capquiz_activity_task.class.php +++ b/backup/moodle2/backup_capquiz_activity_task.class.php @@ -46,7 +46,6 @@ protected function define_my_settings() { */ protected function define_my_steps() { $this->add_step(new backup_capquiz_activity_structure_step('capquiz_structure', 'capquiz.xml')); - // TODO: This might not be necessary in future Moodle versions, if discussed subclass is added. $this->add_step(new backup_calculate_question_categories('activity_question_categories')); $this->add_step(new backup_delete_temp_questions('clean_temp_questions')); } diff --git a/backup/moodle2/backup_capquiz_stepslib.php b/backup/moodle2/backup_capquiz_stepslib.php index 0bd25a8..0bacf96 100644 --- a/backup/moodle2/backup_capquiz_stepslib.php +++ b/backup/moodle2/backup_capquiz_stepslib.php @@ -41,7 +41,14 @@ protected function define_structure() { 'timecreated', 'timemodified', 'published', - 'default_user_rating', + 'defaultuserrating', + 'stars_to_pass', + 'timedue', + 'numquestioncandidates', + 'minquestionsuntilreappearance', + 'userwinprobability', + 'userkfactor', + 'questionkfactor', ]); $questionlist = new backup_nested_element('questionlist', null, [ 'id', @@ -51,15 +58,17 @@ protected function define_structure() { 'description', 'star_ratings', 'is_template', - 'time_created', - 'time_modified', - 'default_question_rating', + 'defaultquestionrating', + 'timecreated', + 'timemodified', ]); $questions = new backup_nested_element('questions'); $question = new backup_nested_element('question', ['id'], [ 'question_id', 'question_list_id', 'rating', + 'timemodified', + 'timecreated', ]); $questionratings = new backup_nested_element('questionratings'); $questionrating = new backup_nested_element('question_rating', ['id'], [ @@ -68,18 +77,6 @@ protected function define_structure() { 'manual', 'timecreated', ]); - $questionselections = new backup_nested_element('questionselections'); - $questionselection = new backup_nested_element('questionselection', ['id'], [ - 'capquiz_id', - 'strategy', - 'configuration', - ]); - $ratingsystems = new backup_nested_element('ratingsystems'); - $ratingsystem = new backup_nested_element('ratingsystem', ['id'], [ - 'capquiz_id', - 'rating_system', - 'configuration', - ]); $users = new backup_nested_element('users'); $user = new backup_nested_element('user', ['id'], [ 'user_id', @@ -87,6 +84,8 @@ protected function define_structure() { 'question_usage_id', 'rating', 'highest_level', + 'timemodified', + 'timecreated', ]); $this->add_question_usages($user, 'question_usage_id'); @@ -112,6 +111,8 @@ protected function define_structure() { 'prev_question_prev_rating_id', 'user_rating_id', 'user_prev_rating_id', + 'timemodified', + 'timecreated', ]); // Build the tree. @@ -121,12 +122,6 @@ protected function define_structure() { $question->add_child($questionratings); $questionratings->add_child($questionrating); - $capquiz->add_child($questionselections); - $questionselections->add_child($questionselection); - - $capquiz->add_child($ratingsystems); - $ratingsystems->add_child($ratingsystem); - $capquiz->add_child($users); $users->add_child($user); $user->add_child($userratings); @@ -139,8 +134,6 @@ protected function define_structure() { $questionlist->set_source_table('capquiz_question_list', ['capquiz_id' => backup::VAR_PARENTID]); $question->set_source_table('capquiz_question', ['question_list_id' => backup::VAR_PARENTID]); $questionrating->set_source_table('capquiz_question_rating', ['capquiz_question_id' => backup::VAR_PARENTID]); - $questionselection->set_source_table('capquiz_question_selection', ['capquiz_id' => backup::VAR_PARENTID]); - $ratingsystem->set_source_table('capquiz_rating_system', ['capquiz_id' => backup::VAR_PARENTID]); if ($this->get_setting_value('userinfo')) { $user->set_source_table('capquiz_user', ['capquiz_id' => backup::VAR_PARENTID]); $userrating->set_source_table('capquiz_user_rating', ['capquiz_user_id' => backup::VAR_PARENTID]); diff --git a/backup/moodle2/restore_capquiz_stepslib.php b/backup/moodle2/restore_capquiz_stepslib.php index ff80470..54da331 100644 --- a/backup/moodle2/restore_capquiz_stepslib.php +++ b/backup/moodle2/restore_capquiz_stepslib.php @@ -33,7 +33,7 @@ class restore_capquiz_activity_structure_step extends restore_questions_activity /** * @var \stdClass for inform_new_usage_id */ - private $currentquestionlist; + private $currentcapuser; /** * Define the structure to be processed by this backup step. @@ -41,13 +41,10 @@ class restore_capquiz_activity_structure_step extends restore_questions_activity protected function define_structure() { $paths = []; $paths[] = new restore_path_element('capquiz', '/activity/capquiz'); - $questionlist = new restore_path_element('capquiz_question_list', '/activity/capquiz/questionlist'); - $paths[] = $questionlist; + $paths[] = new restore_path_element('capquiz_question_list', '/activity/capquiz/questionlist'); $paths[] = new restore_path_element('capquiz_question', '/activity/capquiz/questionlist/questions/question'); $paths[] = new restore_path_element( 'capquiz_question_rating', '/activity/capquiz/questionlist/questions/question/questionratings/question_rating'); - $paths[] = new restore_path_element('capquiz_question_selection', '/activity/capquiz/questionselections/questionselection'); - $paths[] = new restore_path_element('capquiz_rating_system', '/activity/capquiz/ratingsystems/ratingsystem'); if ($this->get_setting_value('userinfo')) { $capuser = new restore_path_element('capquiz_user', '/activity/capquiz/users/user'); $this->add_question_usages($capuser, $paths); @@ -85,8 +82,8 @@ protected function process_capquiz_question_list($data) { $data = (object)$data; $oldid = $data->id; $data->capquiz_id = $this->get_new_parentid('capquiz'); - $data->time_created = $this->apply_date_offset($data->time_created); - $data->time_modified = $this->apply_date_offset($data->time_modified); + $data->timecreated = $this->apply_date_offset($data->timecreated); + $data->timemodified = $this->apply_date_offset($data->timemodified); $data->context_id = \context_course::instance($this->get_courseid())->id; $newitemid = $DB->insert_record('capquiz_question_list', $data); $this->set_mapping('capquiz_question_list', $oldid, $newitemid); @@ -124,34 +121,6 @@ protected function process_capquiz_question_rating($data) { $this->set_mapping('capquiz_question_rating', $oldid, $newitemid); } - /** - * Processes and backs up capquiz question selection - * - * @param object $data - */ - protected function process_capquiz_question_selection($data) { - global $DB; - $data = (object)$data; - $data->capquiz_id = $this->get_new_parentid('capquiz'); - $oldid = $data->id; - $newitemid = $DB->insert_record('capquiz_question_selection', $data); - $this->set_mapping('capquiz_question_selection', $oldid, $newitemid); - } - - /** - * Processes and backs up capquiz question rating system - * - * @param object $data - */ - protected function process_capquiz_rating_system($data) { - global $DB; - $data = (object)$data; - $data->capquiz_id = $this->get_new_parentid('capquiz'); - $oldid = $data->id; - $newitemid = $DB->insert_record('capquiz_rating_system', $data); - $this->set_mapping('capquiz_rating_system', $oldid, $newitemid); - } - /** * Processes and backs up capquiz user * @@ -161,6 +130,8 @@ protected function process_capquiz_user($data) { $data = (object)$data; $data->user_id = $this->get_mappingid('user', $data->user_id); $data->capquiz_id = $this->get_new_parentid('capquiz'); + $data->timecreated = $this->apply_date_offset($data->timecreated); + $data->timemodified = $this->apply_date_offset($data->timemodified); $this->currentcapuser = clone($data); } @@ -195,6 +166,8 @@ protected function process_capquiz_attempt($data) { $data->prev_question_prev_rating_id = $this->get_mappingid('capquiz_question_rating', $data->prev_question_prev_rating_id); $data->user_rating_id = $this->get_mappingid('capquiz_user_rating', $data->user_rating_id); $data->user_prev_rating_id = $this->get_mappingid('capquiz_user_rating', $data->user_prev_rating_id); + $data->timecreated = $this->apply_date_offset($data->timecreated); + $data->timemodified = $this->apply_date_offset($data->timemodified); $newitemid = $DB->insert_record('capquiz_attempt', $data); $this->set_mapping('capquiz_attempt', $oldid, $newitemid); } @@ -204,7 +177,7 @@ protected function process_capquiz_attempt($data) { * * @param int $newusageid */ - protected function inform_new_usage_id($newusageid) { + protected function inform_new_usage_id($newusageid): void { global $DB; $data = $this->currentcapuser; $oldid = $data->id; diff --git a/classes/api.php b/classes/api.php new file mode 100644 index 0000000..d876171 --- /dev/null +++ b/classes/api.php @@ -0,0 +1,566 @@ +. + +declare(strict_types=1); + +namespace mod_capquiz; + +use context_course; +use question_bank; +use question_engine; +use question_usage_by_activity; +use stdClass; + +/** + * CAPQuiz API. + * + * @package mod_capquiz + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class api { + + /** + * Find related CAPQuiz user, or create a new one if it doesn't exist. + * + * @param capquiz $capquiz + * @param int $moodleuserid + */ + public static function get_user(capquiz $capquiz, int $moodleuserid): ?capquiz_user { + $user = capquiz_user::get_record([ + 'user_id' => $moodleuserid, + 'capquiz_id' => $capquiz->get('id'), + ]); + if ($user) { + return $user; + } + $user = new capquiz_user(); + $user->set_many([ + 'user_id' => $moodleuserid, + 'capquiz_id' => $capquiz->get('id'), + 'rating' => $capquiz->get('defaultuserrating'), + ]); + $user->save(); + api::create_user_rating($user, $user->get('rating'), false); + return $user; + } + + /** + * Create a new question usage. + * + * @return question_usage_by_activity + */ + public static function create_question_usage(): question_usage_by_activity { + global $PAGE; + $quba = question_engine::make_questions_usage_by_activity('mod_capquiz', $PAGE->cm->context); + $quba->set_preferred_behaviour('immediatefeedback'); + question_engine::save_questions_usage_by_activity($quba); + return $quba; + } + + /** + * Get question usage for a CAPQuiz user. + * + * @param capquiz_user $user + * @return question_usage_by_activity + */ + public static function get_question_usage_for_user(capquiz_user $user): question_usage_by_activity { + if (!$user->get('question_usage_id')) { + $quba = api::create_question_usage(); + $user->set('question_usage_id', $quba->get_id()); + $user->save(); + } + return question_engine::load_questions_usage_by_activity($user->get('question_usage_id')); + } + + /** + * Creates a new question attempt for a user. + * + * @param capquiz_user $user + * @param capquiz_question $question + */ + public static function create_question_attempt(capquiz_user $user, capquiz_question $question): ?capquiz_question_attempt { + global $DB; + $quba = api::get_question_usage_for_user($user); + $questions = question_load_questions([$question->get('question_id')]); + $targetquestion = reset($questions); + if (!$targetquestion) { + return null; + } + $slot = $quba->add_question(question_bank::make_question($targetquestion)); + $quba->start_question($slot); + question_engine::save_questions_usage_by_activity($quba); + $record = new stdClass(); + $record->slot = $slot; + $record->user_id = $user->get('id'); + $record->question_id = $question->get('id'); + $attemptid = $DB->insert_record('capquiz_attempt', $record); + return new capquiz_question_attempt($attemptid); + } + + /** + * Load information about the latest user rating for an capquiz user from the database. + * + * @param int $capquizuserid + */ + public static function get_latest_user_rating_by_user(int $capquizuserid): ?capquiz_user_rating { + global $DB; + $sql = "SELECT cur.* + FROM {capquiz_user_rating} cur + JOIN {capquiz_user} cu ON cu.id = cur.capquiz_user_id + WHERE cur.id = ( + SELECT MAX(cur2.id) + FROM {capquiz_user_rating} cur2 + JOIN {capquiz_user} cu2 ON cu2.id = cur2.capquiz_user_id + WHERE cu2.id = cu.id + ) + AND cu.id = :capquiz_user_id"; + $record = $DB->get_record_sql($sql, ['capquiz_user_id' => $capquizuserid]); + return empty($record) ? null : new capquiz_user_rating(0, $record); + } + + /** + * Load information about the latest question rating for an attempt from the database. + * + * @param capquiz_question $question + */ + public static function get_latest_question_rating_by_question(capquiz_question $question): ?capquiz_question_rating { + global $DB; + $sql = "SELECT cqr.* + FROM {capquiz_question_rating} cqr + JOIN {capquiz_question} cq ON cq.id = cqr.capquiz_question_id + WHERE cqr.id = ( + SELECT MAX(cqr2.id) + FROM {capquiz_question_rating} cqr2 + JOIN {capquiz_question} cq2 ON cq2.id = cqr2.capquiz_question_id + WHERE cq2.id = cq.id + ) + AND cq.id = :capquizquestionid"; + $record = $DB->get_record_sql($sql, ['capquizquestionid' => $question->get('id')]); + if ($record) { + return new capquiz_question_rating(0, $record); + } + return api::create_question_rating($question->get('id'), $question->get('rating'), false); + } + + /** + * Add question to list. + * + * @param capquiz_question_list $qlist + * @param int $questionid + * @param float $rating + * @return void + */ + public static function add_question_to_list(capquiz_question_list $qlist, int $questionid, float $rating): void { + $question = new capquiz_question(); + $question->set('question_list_id', $qlist->get('id')); + $question->set('question_id', $questionid); + $question->save(); + api::set_question_rating($question->get('id'), $rating, false); + } + + /** + * Create new question rating. + * + * @param int $capquizquestionid + * @param float $rating + * @param bool $manual + */ + public static function create_question_rating(int $capquizquestionid, float $rating, bool $manual): capquiz_question_rating { + $questionrating = new capquiz_question_rating(); + $questionrating->set_many([ + 'capquiz_question_id' => $capquizquestionid, + 'rating' => $rating, + 'manual' => $manual, + ]); + return $questionrating->create(); + } + + /** + * Set question rating. + * + * @param int $capquizquestionid + * @param float $rating + * @param bool $manual + */ + public static function set_question_rating(int $capquizquestionid, float $rating, bool $manual): void { + $question = new capquiz_question($capquizquestionid); + $question->set('rating', $rating); + $question->save(); + api::create_question_rating($question->get('id'), $question->get('rating'), $manual); + } + + /** + * Inserts a new user rating record to the database + * + * @param capquiz_user $user + * @param float $rating + * @param bool $manual + */ + public static function create_user_rating(capquiz_user $user, float $rating, bool $manual): capquiz_user_rating { + $userrating = new capquiz_user_rating(); + $userrating->set_many([ + 'capquiz_user_id' => $user->get('id'), + 'rating' => $rating, + 'manual' => $manual, + ]); + return $userrating->create(); + } + + /** + * Handles answer + * + * @param capquiz $capquiz + * @param capquiz_user $user + * @param capquiz_question_attempt $attempt + */ + public static function attempt_answered(capquiz $capquiz, capquiz_user $user, capquiz_question_attempt $attempt): void { + if (!api::is_question_valid($attempt)) { + return; + } + $userrating = api::get_latest_user_rating_by_user($user->get('id')); + $quba = api::get_question_usage_for_user($user); + $quba->process_action($attempt->get('slot'), $quba->extract_responses($attempt->get('slot'))); + $quba->finish_question($attempt->get('slot'), time()); + question_engine::save_questions_usage_by_activity($quba); + $attempt->set_many([ + 'answered' => 1, + 'time_answered' => time(), + 'user_prev_rating_id' => $userrating->get('id'), + ]); + $attempt->save(); + $question = new capquiz_question($attempt->get('question_id')); + if (api::is_question_correctly_answered($attempt, $user)) { + api::update_user_rating($capquiz->get('userkfactor'), $user, $question->get('rating'), 1); + for ($star = $capquiz->get_max_stars(); $star > 0; $star--) { + $required = $capquiz->get_required_rating_for_star($star); + if ($user->get('rating') >= $required && $user->get('highest_level') < $star) { + $user->set('highest_level', $star); + $user->save(); + break; + } + } + } else { + api::update_user_rating($capquiz->get('userkfactor'), $user, $question->get('rating'), 0); + } + $attempt->set('user_rating_id', $userrating->get('id')); + $attempt->save(); + $previousattempts = capquiz_question_attempt::get_records(['user_id' => $user->get('id')], 'time_reviewed', 'desc', 0, 1); + if ($previousattempts) { + api::update_question_rating($capquiz, reset($previousattempts), $attempt, $user); + } + } + + /** + * Returns true if the answer is correct + * + * @param capquiz_question_attempt $attempt + * @param capquiz_user $user + */ + private static function is_question_correctly_answered(capquiz_question_attempt $attempt, capquiz_user $user): bool { + if (!$attempt->get('answered')) { + return false; + } + $quba = api::get_question_usage_for_user($user); + $moodleattempt = $quba->get_question_attempt($attempt->get('slot')); + return $moodleattempt->get_state()->is_correct(); + } + + /** + * Checks if the question is valid + * + * @param capquiz_question_attempt $attempt + */ + public static function is_question_valid(capquiz_question_attempt $attempt): bool { + global $DB; + $sql = 'SELECT cq.id + FROM {capquiz_attempt} ca + JOIN {capquiz_question} cq + ON ca.question_id = cq.id + WHERE ca.id = :attemptid'; + return $DB->get_record_sql($sql, ['attemptid' => $attempt->get('id')]) !== false; + } + + /** + * Updates the user's ELO rating + * + * @param float $kfactor + * @param capquiz_user $user + * @param float $questionrating + * @param float $score + */ + public static function update_user_rating(float $kfactor, capquiz_user $user, float $questionrating, float $score): void { + $currentrating = $user->get('rating'); + $newrating = $currentrating + $kfactor * ($score - api::elo_expected_result($currentrating, $questionrating)); + $user->set('rating', $newrating); + $user->save(); + api::create_user_rating($user, $newrating, false); + } + + /** + * Updates the winning and losing question ELO ratings + * + * @param float $kfactor + * @param capquiz_question $winner + * @param capquiz_question $loser + */ + public static function update_question_ratings(float $kfactor, capquiz_question $winner, capquiz_question $loser): void { + $loserating = $loser->get('rating'); + $winrating = $winner->get('rating'); + $newloserating = $loserating + $kfactor * (0 - api::elo_expected_result($loserating, $winrating)); + $newwinrating = $winrating + $kfactor * (1 - api::elo_expected_result($winrating, $loserating)); + api::set_question_rating($loser->get('id'), $newloserating, false); + api::set_question_rating($winner->get('id'), $newwinrating, false); + } + + /** + * Calculates the expected score in favor of the player with rating $a, against a player with rating $b + * + * @param float $a + * @param float $b + */ + private static function elo_expected_result(float $a, float $b): float { + return 1.0 / (1.0 + pow(10.0, ($b - $a) / 400.0)); + } + + /** + * Updates the question ratings + * + * @param capquiz $capquiz + * @param capquiz_question_attempt $previous + * @param capquiz_question_attempt $current + * @param capquiz_user $user + */ + private static function update_question_rating(capquiz $capquiz, capquiz_question_attempt $previous, + capquiz_question_attempt $current, capquiz_user $user): void { + $currentcorrect = api::is_question_correctly_answered($current, $user); + $previouscorrect = api::is_question_correctly_answered($previous, $user); + $currentquestion = new capquiz_question($current->get('question_id')); + $previousquestion = new capquiz_question($previous->get('question_id')); + $current->set('prev_question_prev_rating_id', api::get_latest_question_rating_by_question($previousquestion)->get('id')); + $current->set('question_prev_rating_id', api::get_latest_question_rating_by_question($currentquestion)->get('id')); + $current->save(); + if ($previouscorrect && !$currentcorrect) { + api::update_question_ratings($capquiz->get('questionkfactor'), $currentquestion, $previousquestion); + } else if (!$previouscorrect && $currentcorrect) { + api::update_question_ratings($capquiz->get('questionkfactor'), $previousquestion, $currentquestion); + } else { + api::create_question_rating($previousquestion->get('id'), $previousquestion->get('rating'), false); + api::create_question_rating($currentquestion->get('id'), $currentquestion->get('rating'), false); + } + $current->set('prev_question_rating_id', api::get_latest_question_rating_by_question($previousquestion)->get('id')); + $current->set('question_rating_id', api::get_latest_question_rating_by_question($currentquestion)->get('id')); + $current->save(); + } + + /** + * Selects the next question for the user based on the configuration + * + * @param capquiz $capquiz + * @param capquiz_user $user + * @param capquiz_question_attempt[] $inactiveattempts + */ + public static function n_closest_next_question(capquiz $capquiz, capquiz_user $user, array $inactiveattempts): ?capquiz_question { + $excluded = api::determine_excluded_questions($capquiz, $inactiveattempts); + $candidates = api::find_questions_closest_to_rating($capquiz, $user, $excluded); + if (empty($candidates)) { + return null; + } + $index = mt_rand(0, count($candidates) - 1); + return $candidates[$index]; + } + + /** + * Gets an attempt for the user, returns a new one if there are no active attempts + * + * @param capquiz $capquiz + * @param capquiz_user $user + */ + public static function get_question_attempt_for_user(capquiz $capquiz, capquiz_user $user): ?capquiz_question_attempt { + $attempt = capquiz_question_attempt::get_record([ + 'user_id' => $user->get('id'), + 'reviewed' => 0, + ]); + if ($attempt) { + return $attempt; + } + $inactiveattempts = capquiz_question_attempt::get_records([ + 'user_id' => $user->get('id'), + 'answered' => 1, + 'reviewed' => 1, + ]); + $qlist = capquiz_question_list::get_record(['capquiz_id' => $capquiz->get('id')]); + $question = match ($capquiz->get('questionselectiontype')) { + 'nclosest' => api::n_closest_next_question($capquiz, $user, $inactiveattempts), + 'chronological' => api::chronologic_next_question($qlist, $inactiveattempts), + default => null, + }; + if (!$question) { + return null; + } + return api::create_question_attempt($user, $question); + } + + /** + * Returns the course record related to the given question. + * + * @return ?stdClass course object + */ + public static function get_course_for_question(capquiz_question $question): ?stdClass { + global $DB; + $sql = 'SELECT c.id AS id + FROM {capquiz_question} cq + JOIN {question} q ON q.id = cq.question_id + JOIN {question_versions} qv + ON q.id = qv.questionid + JOIN {question_bank_entries} qbe + ON qbe.id = qv.questionbankentryid + JOIN {question_categories} qc + ON qc.id = qbe.questioncategoryid + JOIN {context} ctx ON ctx.id = qc.contextid + LEFT JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = 70 + JOIN {course} c ON (ctx.contextlevel = 50 AND c.id = ctx.instanceid) + OR (ctx.contextlevel = 70 AND c.id = cm.course) + WHERE cq.id = :questionid'; + return $DB->get_record_sql($sql, ['questionid' => $question->get('id')]) ?: null; + } + + /** + * Returns the completion level to the next rating as a percent value + * + * @param capquiz $capquiz + * @param float $userrating + */ + public static function get_percent_to_next_star(capquiz $capquiz, float $userrating): int { + $nextstarrating = 0; + for ($star = 1; $star <= $capquiz->get_max_stars(); $star++) { + $nextstarrating = $capquiz->get_required_rating_for_star($star); + if ($nextstarrating > $userrating) { + $previous = $star === 1 ? $capquiz->get('defaultuserrating') : $capquiz->get_required_rating_for_star($star - 1); + $userrating -= $previous; + $nextstarrating -= $previous; + break; + } + } + return $nextstarrating >= 1 ? (int)($userrating / $nextstarrating * 100) : 0; + } + + /** + * Creates a copy of this instance and inserts the new copy into the database + * + * @param int $qlistid + * @param ?int $capquizid + * @return capquiz_question_list + */ + public static function copy_question_list(int $qlistid, ?int $capquizid): capquiz_question_list { + global $DB, $PAGE; + $transaction = $DB->start_delegated_transaction(); + $newqlist = new capquiz_question_list(); + $newqlist->set_many([ + 'capquiz_id' => $capquizid, + 'context_id' => context_course::instance($PAGE->course->id)->id, + 'is_template' => $capquizid === null, + ]); + $newqlist->save(); + foreach (capquiz_question::get_records(['question_list_id' => $qlistid]) as $sourcequestion) { + $question = new capquiz_question(); + $question->set('question_list_id', $newqlist->get('id')); + $question->set('question_id', $sourcequestion->get('question_id')); + $question->save(); + api::set_question_rating($question->get('id'), $sourcequestion->get('rating'), false); + } + $DB->commit_delegated_transaction($transaction); + return $newqlist; + } + + /** + * Returns the next question for the user in a chronological order + * + * @param capquiz_question_list $qlist + * @param capquiz_question_attempt[] $inactiveattempts + */ + private static function chronologic_next_question(capquiz_question_list $qlist, array $inactiveattempts): ?capquiz_question { + foreach (capquiz_question::get_records(['question_list_id' => $qlist->get('id')]) as $question) { + $answered = false; + foreach ($inactiveattempts as $inactiveattempt) { + if ($inactiveattempt->get('question_id') === $question->get('id')) { + $answered = true; + break; + } + } + if (!$answered) { + return $question; + } + } + return null; + } + + /** + * Returns the ideal question rating + * + * @param float $winprobability + * @param float $rating + */ + private static function ideal_question_rating(float $winprobability, float $rating): float { + return 400.0 * log((1.0 / $winprobability) - 1.0, 10.0) + $rating; + } + + /** + * Identifies questions to exclude and returns them in an array + * + * @param capquiz $capquiz + * @param capquiz_question_attempt[] $inactiveattempts + */ + private static function determine_excluded_questions(capquiz $capquiz, array $inactiveattempts): array { + $it = new \ArrayIterator(array_reverse($inactiveattempts, true)); + $excluded = []; + for ($i = 0; $i < $capquiz->get('minquestionsuntilreappearance'); $i++) { + if (!$it->valid()) { + break; + } + $excluded[] = $it->current()->get('question_id'); + $it->next(); + } + return array_unique($excluded); + } + + /** + * Finds the questions closest to the users rating + * + * @param capquiz $capquiz + * @param capquiz_user $user + * @param array $excludedquestions + */ + private static function find_questions_closest_to_rating(capquiz $capquiz, capquiz_user $user, array $excludedquestions): array { + global $DB; + $sql = 'SELECT * FROM {capquiz_question} WHERE question_list_id = ?'; + $sql .= str_repeat(' AND id <> ?', count($excludedquestions)); + $sql .= ' ORDER BY ABS(? - rating)'; + $params = []; + $qlist = capquiz_question_list::get_record(['capquiz_id' => $capquiz->get('id')]); + $params[] = $qlist->get('id'); + if (count($excludedquestions) > 0) { + array_push($params, ...$excludedquestions); + } + $params[] = api::ideal_question_rating($capquiz->get('userwinprobability'), $user->get('rating')); + $questions = []; + foreach ($DB->get_records_sql($sql, $params, 0, $capquiz->get('numquestioncandidates')) as $questionentry) { + $questions[] = new capquiz_question(0, $questionentry); + } + return $questions; + } +} diff --git a/classes/bank/add_to_quiz_action.php b/classes/bank/add_to_quiz_action.php deleted file mode 100644 index 6cb9496..0000000 --- a/classes/bank/add_to_quiz_action.php +++ /dev/null @@ -1,46 +0,0 @@ -. - -namespace mod_capquiz\bank; - -use mod_capquiz\capquiz_urls; -use stdClass; - -/** - * Question bank action to add question to quiz. - * - * @package mod_capquiz - * @copyright 2024 NTNU - * @author 2024 Sebastian Gundersen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class add_to_quiz_action extends \core_question\local\bank\question_action_base { - /** - * Get the information required to display this action either as a menu item or a separate action column. - * - * @param stdClass $question the row from the $question table, augmented with extra information. - * @return array with three elements. - * $url - the URL to perform the action. - * $icon - the icon for this action. E.g. 't/delete'. - * $label - text label to display in the UI (either in the menu, or as a tool-tip on the icon) - */ - protected function get_url_icon_and_label(stdClass $question): array { - if (!question_has_capability_on($question, 'use')) { - return [null, null, null]; - } - return [capquiz_urls::add_question_to_list_url($question->id), 't/add', get_string('addtoquiz', 'quiz')]; - } -} diff --git a/classes/capquiz.php b/classes/capquiz.php index 23a849e..36dab93 100755 --- a/classes/capquiz.php +++ b/classes/capquiz.php @@ -14,278 +14,141 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines the class capquiz representing a capquiz - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @author Aleksander Skrede - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace mod_capquiz; -use context_module; -use moodle_page; -use renderer_base; -use stdClass; +use core\persistent; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/questionlib.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/capquiz_rating_system_loader.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/capquiz_matchmaking_strategy_loader.php'); /** - * Class capquiz + * CAPQuiz instance. * * @package mod_capquiz - * @author Sebastian S. Gundersen - * @author Aleksander Skrede - * @copyright 2019 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquiz { - - /** @var context_module $context */ - private $context; - - /** @var stdClass $cm */ - private $cm; - - /** @var stdClass $courserecord */ - private $courserecord; - - /** @var stdClass $record */ - private $record; - - /** @var renderer_base $renderer */ - private renderer_base $renderer; - - /** @var ?capquiz_question_list $qlist */ - private ?capquiz_question_list $qlist; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * Constructor. - * - * @param int $cmid - */ - public function __construct(int $cmid) { - global $DB, $PAGE; - $this->cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); - $this->context = context_module::instance($cmid); - $PAGE->set_context($this->context); - $this->renderer = $PAGE->get_renderer('mod_capquiz'); - $this->courserecord = $DB->get_record('course', ['id' => $this->cm->course], '*', MUST_EXIST); - $this->record = $DB->get_record('capquiz', ['id' => $this->cm->instance], '*', MUST_EXIST); - $this->qlist = capquiz_question_list::load_question_list($this); - $this->page = $PAGE; - } +class capquiz extends persistent { - /** - * Updates the grades if the grading is completed or if forced - * - * @param bool $force - */ - public function update_grades(bool $force = false): void { - if (!$this->is_grading_completed() || $force) { - capquiz_update_grades($this->record); - } - } - - /** - * Returns the page of the CapQuiz - */ - public function get_page(): moodle_page { - return $this->page; - } - - /** - * Returns the capquiz' id - */ - public function id(): int { - return $this->record->id; - } - - /** - * Returns the capquiz' name - */ - public function name(): string { - return $this->record->name; - } - - /** - * Returns true if the capquiz is published - */ - public function is_published(): bool { - return $this->record->published; - } + /** @var string The table name. */ + const TABLE = 'capquiz'; /** * Returns true if the capquiz is completely graded */ public function is_grading_completed(): bool { - return $this->record->timedue < time() && $this->record->timedue > 0; - } - - /** - * Returns the amount of stars needed to pass - */ - public function stars_to_pass(): int { - return $this->record->stars_to_pass; - } - - /** - * Returns the time when the quiz is due - */ - public function time_due(): int { - return $this->record->timedue; - } - - /** - * Sets a new value for stars to pass - * - * @param int $stars - */ - public function set_stars_to_pass(int $stars): void { - global $DB; - $this->record->stars_to_pass = $stars; - $DB->update_record('capquiz', $this->record); + return $this->get('timedue') < time() && $this->get('timedue') > 0; } /** - * Sets a new due time - * - * @param int $time + * Returns the count of all ratings (The value needed for a full star score) */ - public function set_time_due(int $time): void { - global $DB; - $this->record->timedue = $time; - $DB->update_record('capquiz', $this->record); + public function get_max_stars(): int { + return substr_count($this->get('starratings'), ',') + 1; } /** - * Sets a new default rating + * Get the required rating for a given star. * - * @param float $rating + * @param int $star */ - public function set_default_user_rating(float $rating): void { - global $DB; - $this->record->default_user_rating = $rating; - $DB->update_record('capquiz', $this->record); - } - - /** - * Publishes the capquiz if it can publish it - */ - public function publish(): bool { - global $DB; - if (!$this->can_publish()) { - return false; + public function get_required_rating_for_star(int $star): float { + $index = $star - 1; + $ratings = explode(',', $this->get('starratings')); + if ($index >= count($ratings)) { + return (float)$ratings[count($ratings) - 1]; } - $this->record->published = true; - $DB->update_record('capquiz', $this->record); - return $this->is_published(); + return (float)$ratings[$index]; } /** - * Returns true if the capquiz is publishable - */ - public function can_publish(): bool { - if (!$this->has_question_list() || $this->is_published()) { - return false; - } - return $this->question_list()->has_questions(); - } - - /** - * Returns a new question engine based on the user + * Return the definition of the properties of this model. * - * @param capquiz_user $user - */ - public function question_engine(capquiz_user $user): ?capquiz_question_engine { - $quba = $user->question_usage(); - if (!$quba) { - return null; - } - $ratingsystemloader = new capquiz_rating_system_loader($this); - $strategyloader = new capquiz_matchmaking_strategy_loader($this); - return new capquiz_question_engine($this, $quba, $strategyloader, $ratingsystemloader); - } - - /** - * Returns the capquiz user - */ - public function user(): ?capquiz_user { - global $USER; - return capquiz_user::load_user($this, $USER->id, $this->context()); - } - - /** - * Returns the default rating - */ - public function default_user_rating(): float { - return $this->record->default_user_rating; + * @return array + */ + protected static function define_properties(): array { + return [ + 'course' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'name' => [ + 'type' => PARAM_TEXT, + 'null' => NULL_NOT_ALLOWED, + ], + 'intro' => [ + 'type' => PARAM_TEXT, + 'null' => NULL_ALLOWED, + ], + 'introformat' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'published' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'defaultuserrating' => [ + 'type' => PARAM_FLOAT, + 'default' => 1200.0, + 'null' => NULL_NOT_ALLOWED, + ], + 'defaultquestionrating' => [ + 'type' => PARAM_FLOAT, + 'default' => 600.0, + 'null' => NULL_NOT_ALLOWED, + ], + 'starratings' => [ + 'type' => PARAM_TEXT, + 'default' => '1300,1450,1600,1800,2000', + 'null' => NULL_NOT_ALLOWED, + ], + 'stars_to_pass' => [ + 'type' => PARAM_INT, + 'default' => 3, + 'null' => NULL_NOT_ALLOWED, + ], + 'timedue' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'questionselectiontype' => [ + 'type' => PARAM_TEXT, + 'default' => 'nclosest', + 'null' => NULL_NOT_ALLOWED, + ], + 'numquestioncandidates' => [ + 'type' => PARAM_INT, + 'default' => 10, + 'null' => NULL_NOT_ALLOWED, + ], + 'minquestionsuntilreappearance' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'userwinprobability' => [ + 'type' => PARAM_FLOAT, + 'default' => 0.75, + 'null' => NULL_NOT_ALLOWED, + ], + 'userkfactor' => [ + 'type' => PARAM_FLOAT, + 'default' => 32.0, + 'null' => NULL_NOT_ALLOWED, + ], + 'questionkfactor' => [ + 'type' => PARAM_FLOAT, + 'default' => 8.0, + 'null' => NULL_NOT_ALLOWED, + ], + ]; } - - /** - * Returns true if the capquiz has a question list - */ - public function has_question_list(): bool { - return $this->qlist !== null; - } - - /** - * Returns the quiz' question list - */ - public function question_list(): ?capquiz_question_list { - return $this->qlist; - } - - /** - * Returns the current context - */ - public function context(): context_module { - return $this->context; - } - - /** - * Returns teh current course module - */ - public function course_module(): stdClass { - return $this->cm; - } - - /** - * Returns the current course - */ - public function course(): stdClass { - return $this->courserecord; - } - - /** - * Returns the current renderer - */ - public function renderer(): renderer_base { - return $this->renderer; - } - - /** - * Validates the matchmaking and rating systems - */ - public function validate_matchmaking_and_rating_systems(): void { - $ratingsystemloader = new capquiz_rating_system_loader($this); - if (!$ratingsystemloader->has_rating_system()) { - $ratingsystemloader->set_default_rating_system(); - } - $strategyloader = new capquiz_matchmaking_strategy_loader($this); - if (!$strategyloader->has_strategy()) { - $strategyloader->set_default_strategy(); - } - } - } diff --git a/classes/capquiz_action_performer.php b/classes/capquiz_action_performer.php deleted file mode 100755 index 8e5e304..0000000 --- a/classes/capquiz_action_performer.php +++ /dev/null @@ -1,264 +0,0 @@ -. - -/** - * This file defines a class used to perform different actions on the capquiz - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/questionlib.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/capquiz_rating_system_loader.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/capquiz_matchmaking_strategy_loader.php'); - -/** - * Class capquiz_action_performer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_action_performer { - - /** - * Performs an action on a capquiz - * - * @param string $action The action to perform - * @param capquiz $capquiz The capquiz on which the action will be performed - */ - public static function perform(string $action, capquiz $capquiz): void { - switch ($action) { - case 'redirect': - self::redirect(); - break; - case 'set-question-list': - self::assign_question_list($capquiz); - break; - case 'add-question': - self::add_question_to_list($capquiz); - break; - case 'remove-question': - self::remove_question_from_list($capquiz); - break; - case 'publish-question-list': - self::publish_capquiz($capquiz); - break; - case 'set-question-rating': - self::set_question_rating($capquiz); - break; - case 'set-default-question-rating': - self::set_default_question_rating($capquiz); - break; - case 'create-question-list-template': - self::create_question_list_template($capquiz); - break; - case 'merge_qlist': - self::merge_question_list($capquiz); - break; - case 'delete_qlist': - self::delete_question_list(); - break; - case 'regrade-all': - $capquiz->update_grades(true); - capquiz_urls::redirect_to_url(capquiz_urls::view_classlist_url()); - break; - default: - break; - } - } - - /** - * Redirects the user to another page - */ - public static function redirect(): void { - $url = optional_param('target-url', null, PARAM_TEXT); - if ($url) { - capquiz_urls::redirect_to_url(new \moodle_url($url)); - } - } - - /** - * Assigns a question list to the capquiz - * - * @param capquiz $capquiz - */ - public static function assign_question_list(capquiz $capquiz): void { - $qlistid = optional_param('question-list-id', 0, PARAM_INT); - $qlist = capquiz_question_list::load_any($qlistid); - if ($qlist) { - $capquiz->validate_matchmaking_and_rating_systems(); - $qlist->create_instance_copy($capquiz); - } - } - - /** - * Adds questions from the capquiz' question to the capquiz - * - * @param capquiz $capquiz - */ - public static function add_question_to_list(capquiz $capquiz): void { - $qlist = $capquiz->question_list(); - $questionids = required_param('question-id', PARAM_TEXT); - $questionids = explode(',', $questionids); - foreach ($questionids as $questionid) { - self::create_capquiz_question((int)$questionid, $qlist, $qlist->default_question_rating()); - } - capquiz_urls::redirect_to_previous(); - } - - /** - * Removes a question - * - * @param capquiz $capquiz - */ - public static function remove_question_from_list(capquiz $capquiz): void { - $questionid = optional_param('question-id', 0, PARAM_INT); - if ($questionid && $capquiz->has_question_list()) { - self::remove_capquiz_question($questionid, $capquiz->question_list()->id()); - } - capquiz_urls::redirect_to_previous(); - } - - /** - * Publishes the capquiz - * - * @param capquiz $capquiz - */ - public static function publish_capquiz(capquiz $capquiz): void { - $capquiz->publish(); - } - - /** - * Sets a new question rating on a question - * - * @param capquiz $capquiz - */ - public static function set_question_rating(capquiz $capquiz): void { - $questionid = required_param('question-id', PARAM_INT); - $question = $capquiz->question_list()->question($questionid); - if (!$question) { - throw new \Exception('The specified question does not exist'); - } - $rating = optional_param('rating', null, PARAM_FLOAT); - if ($rating !== null) { - $question->set_rating($rating, true); - } - capquiz_urls::redirect_to_url(capquiz_urls::view_question_list_url()); - } - - /** - * Sets a new default question rating - * - * @param capquiz $capquiz - */ - public static function set_default_question_rating(capquiz $capquiz): void { - $rating = optional_param('rating', null, PARAM_FLOAT); - if ($rating !== null) { - $capquiz->question_list()->set_default_question_rating($rating); - } - capquiz_urls::redirect_to_url(capquiz_urls::view_question_list_url()); - } - - /** - * Creates a template from the capquiz' current question list - * - * @param capquiz $capquiz - * @return capquiz_question_list - */ - public static function create_question_list_template(capquiz $capquiz): capquiz_question_list { - $qlist = $capquiz->question_list(); - if (!$qlist) { - throw new \Exception('Failed to find question list for this CAPQuiz.'); - } else if (!$qlist->has_questions()) { - throw new \Exception('Attempted to create template without questions.'); - } - $qlistcopy = $qlist->create_template_copy($capquiz); - if ($qlistcopy === null) { - throw new \Exception('Failed to create a template from this question list.'); - } - return $qlistcopy; - } - - /** - * Creates a new capquiz question and adds it to the database - * - * @param int $questionid - * @param capquiz_question_list $list - * @param float $rating - */ - private static function create_capquiz_question(int $questionid, capquiz_question_list $list, float $rating): void { - global $DB; - if ($questionid === 0) { - return; - } - $ratedquestion = new stdClass(); - $ratedquestion->question_list_id = $list->id(); - $ratedquestion->question_id = $questionid; - $ratedquestion->rating = $rating; - $capquizquestionid = $DB->insert_record('capquiz_question', $ratedquestion, true); - capquiz_question_rating::insert_question_rating_entry($capquizquestionid, $rating); - } - - /** - * Removes matching questions from the database - * - * @param int $questionid - * @param int $qlistid - */ - private static function remove_capquiz_question(int $questionid, int $qlistid): void { - global $DB; - $DB->delete_records('capquiz_question', ['id' => $questionid, 'question_list_id' => $qlistid]); - } - - /** - * Merges the capquiz' question list to its current context - * - * @param capquiz $capquiz - */ - private static function merge_question_list(capquiz $capquiz): void { - global $DB; - $srcqlistid = required_param('qlistid', PARAM_INT); - $srcqlistrecord = $DB->get_record('capquiz_question_list', ['id' => $srcqlistid]); - if ($srcqlistrecord) { - $capquiz->question_list()->merge(new capquiz_question_list($srcqlistrecord)); - } - capquiz_urls::redirect_to_url(capquiz_urls::view_question_list_url()); - } - - /** - * Deletes the question list and its questions from the database - */ - private static function delete_question_list(): void { - global $DB; - $srcqlistid = required_param('qlistid', PARAM_INT); - $DB->delete_records('capquiz_question', ['question_list_id' => $srcqlistid]); - $DB->delete_records('capquiz_question_list', ['id' => $srcqlistid]); - capquiz_urls::redirect_to_url(capquiz_urls::view_import_url()); - } - -} diff --git a/classes/capquiz_matchmaking_strategy.php b/classes/capquiz_matchmaking_strategy.php deleted file mode 100755 index 8736d33..0000000 --- a/classes/capquiz_matchmaking_strategy.php +++ /dev/null @@ -1,67 +0,0 @@ -. - -/** - * This file defines a class that represents a capquiz matchmaking strategy - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use stdClass; - -/** - * Class capquiz_matchmaking_strategy - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -abstract class capquiz_matchmaking_strategy { - - /** - * Sets a new matchmaking strategy configuration - * - * @param stdClass $configuration - */ - abstract public function configure(stdClass $configuration): void; - - /** - * Returns the current configuration - */ - abstract public function configuration(): stdClass; - - /** - * Returns the default configuration - */ - abstract public function default_configuration(): stdClass; - - /** - * Returns a new question for the user based on the matchmaking strategy configuration - * - * @param capquiz_user $user - * @param capquiz_question_list $qlist - * @param array $inactiveattempts - */ - abstract public function next_question_for_user(capquiz_user $user, capquiz_question_list $qlist, - array $inactiveattempts): ?capquiz_question; - -} diff --git a/classes/capquiz_matchmaking_strategy_loader.php b/classes/capquiz_matchmaking_strategy_loader.php deleted file mode 100755 index ee1d8cf..0000000 --- a/classes/capquiz_matchmaking_strategy_loader.php +++ /dev/null @@ -1,227 +0,0 @@ -. - -/** - * This file defines a class used to load capquiz matchmaking strategies - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use moodle_url; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/capquiz/classes/matchmaking/capquiz_matchmaking_strategy_registry.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/matchmaking/chronologic/chronologic_selector.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/matchmaking/n_closest/n_closest_selector.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/matchmaking/n_closest/n_closest_configuration_form.php'); - -/** - * Class capquiz_matchmaking_strategy_loader - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_matchmaking_strategy_loader { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var ?stdClass $record */ - private ?stdClass $record = null; - - /** @var capquiz_matchmaking_strategy_registry $registry */ - private capquiz_matchmaking_strategy_registry $registry; - - /** @var ?stdClass $configuration */ - private ?stdClass $configuration; - - /** - * Constructor. - * - * @param capquiz $capquiz - */ - public function __construct(capquiz $capquiz) { - $this->capquiz = $capquiz; - $this->registry = new capquiz_matchmaking_strategy_registry($capquiz); - $this->load_configuration(); - } - - /** - * Returns localized strategy name - * - * @param string $name - */ - public static function localized_strategy_name(string $name): string { - // TODO: This is a hack. The database records currently store the names, which makes localization hard. - return match ($name) { - 'N-closest' => get_string('n_closest', 'capquiz'), - 'Chronological' => get_string('chronological', 'capquiz'), - default => get_string('no_strategy_specified', 'capquiz'), - }; - } - - /** - * Returns the selected strategy - * - * @return ?capquiz_matchmaking_strategy - */ - public function selector(): ?capquiz_matchmaking_strategy { - if (!$this->record) { - return null; - } - $strategy = $this->registry->selector($this->record->strategy); - if ($this->configuration) { - $strategy->configure($this->configuration); - } - return $strategy; - } - - /** - * Returns configuration form for the current matchmaking strategy - * - * @param moodle_url $url - */ - public function configuration_form(moodle_url $url): mixed { - if ($this->record && $this->configuration) { - return $this->registry->configuration_form($this->record->strategy, $this->configuration, $url); - } - return null; - } - - /** - * Returns true if this instance has a strategy set - */ - public function has_strategy(): bool { - return $this->selector() !== null; - } - - /** - * Returns the name of the current strategy - */ - public function current_strategy_name(): string { - if ($this->record) { - return $this->record->strategy; - } - return get_string('no_strategy_specified', 'capquiz'); - } - - /** - * Configures teh current strategy - * - * @param stdClass $candidateconfig - */ - public function configure_current_strategy(stdClass $candidateconfig): void { - if (!$this->record) { - return; - } - $selector = $this->selector(); - $selector->configure($candidateconfig); - $config = $selector->configuration(); - $this->record->configuration = empty((array)$config) ? '' : $this->serialize($config); - $this->update_configuration($this->record); - } - - /** - * Sets the default strategy - */ - public function set_default_strategy(): void { - $this->set_strategy($this->registry->default_selection_strategy()); - } - - /** - * Sets strategy based on the strategy name - * - * @param string $strategy - */ - public function set_strategy(string $strategy): void { - $selector = $this->registry->selector($strategy); - $record = new stdClass; - $record->strategy = $strategy; - $record->capquiz_id = $this->capquiz->id(); - $defaultconfig = $selector->default_configuration(); - $record->configuration = empty((array)$defaultconfig) ? '' : $this->serialize($defaultconfig); - global $DB; - if ($this->record) { - $record->id = $this->record->id; - $this->update_configuration($record); - } else { - $DB->insert_record('capquiz_question_selection', $record); - $this->set_configuration($record); - } - } - - /** - * Loads the strategy configuration from the database - */ - private function load_configuration(): void { - global $DB; - $conditions = ['capquiz_id' => $this->capquiz->id()]; - $config = $DB->get_record('capquiz_question_selection', $conditions); - if ($config) { - $this->set_configuration($config); - } - } - - /** - * Updates the strategy configuration and updates the database record - * - * @param stdClass $config - */ - private function update_configuration(stdClass $config): void { - global $DB; - if ($DB->update_record('capquiz_question_selection', $config)) { - $this->set_configuration($config); - } - } - - /** - * Sets this configuration as a new configuration - * - * @param stdClass $record - */ - private function set_configuration(stdClass $record): void { - $this->record = $record; - $this->configuration = $this->deserialize($record->configuration) ?: null; - } - - /** - * Returns the current configuration as a JSON string - * - * @param stdClass $configuration - */ - private function serialize(stdClass $configuration): string { - return json_encode($configuration); - } - - /** - * Takes in JSON encoded configuration string and returns a decoded configuration - * - * @param string $configuration - */ - private function deserialize(string $configuration): mixed { - return json_decode($configuration, false); - } - -} diff --git a/classes/capquiz_question.php b/classes/capquiz_question.php index 39bb865..79ebcac 100755 --- a/classes/capquiz_question.php +++ b/classes/capquiz_question.php @@ -14,171 +14,45 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class representing a capquiz question - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace mod_capquiz; -use stdClass; +use core\persistent; /** - * Class capquiz_question + * CAPQuiz question. * * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2018 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquiz_question { - - /** @var stdClass $record */ - private stdClass $record; +class capquiz_question extends persistent { - /** @var capquiz_question_rating $rating */ - private capquiz_question_rating $rating; + /** @var string The table name. */ + const TABLE = 'capquiz_question'; /** - * Constructor. + * Return the definition of the properties of this model. * - * @param stdClass $record - */ - public function __construct(stdClass $record) { - global $DB; - $this->record = $record; - // TODO: This query should probably be done in question list. - $question = $DB->get_record('question', ['id' => $record->question_id]); - if ($question !== false) { - $this->record->name = $question->name; - $this->record->text = $question->questiontext; - } else { - $this->record->name = get_string('missing_question', 'capquiz'); - $this->record->text = $this->record->name; - } - $rating = capquiz_question_rating::latest_question_rating_by_question($record->id); - if ($rating === null) { - $this->rating = capquiz_question_rating::insert_question_rating_entry($this->id(), $this->rating()); - } else { - $this->rating = $rating; - } + * @return array + */ + protected static function define_properties(): array { + return [ + 'question_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'question_list_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'rating' => [ + 'type' => PARAM_FLOAT, + 'default' => 0.0, + 'null' => NULL_NOT_ALLOWED, + ], + ]; } - - /** - * Loads a specific question from the database - * - * @param int $questionid - */ - public static function load(int $questionid): ?capquiz_question { - global $DB; - $record = $DB->get_record('capquiz_question', ['id' => $questionid]); - return empty($record) ? null : new capquiz_question($record); - } - - /** - * Returns this questions database entry - */ - public function entry(): stdClass { - return $this->record; - } - - /** - * Returns this questions database entry id - */ - public function id(): int { - return $this->record->id; - } - - /** - * Returns this question's question bank question id - */ - public function question_id(): int { - return $this->record->question_id; - } - - /** - * Returns the id of the question list this question is in - */ - public function question_list_id(): int { - return $this->record->question_list_id; - } - - /** - * Returns this questions rating - */ - public function rating(): float { - return $this->record->rating; - } - - /** - * Returns this questions capquiz question rating - * - * @return capquiz_question_rating - */ - public function get_capquiz_question_rating(): capquiz_question_rating { - return $this->rating; - } - - /** - * Sets this questions rating and capquiz question rating - * - * @param float $rating - * @param bool $manual - */ - public function set_rating(float $rating, bool $manual = false) { - global $DB; - $this->record->rating = $rating; - $DB->update_record('capquiz_question', $this->record); - $questionrating = capquiz_question_rating::create_question_rating($this, $rating, $manual); - $this->rating = $questionrating; - } - - /** - * Returns this questions name - */ - public function name(): string { - return $this->record->name; - } - - /** - * Returns this questions text - * - * @return string - */ - public function text(): string { - return $this->record->text; - } - - /** - * Returns the id of the course this question is in - * - * @return int - * @throws \dml_exception - */ - public function course_id(): int { - global $DB; - $sql = 'SELECT c.id AS id - FROM {capquiz_question} cq - JOIN {question} q ON q.id = cq.question_id - JOIN {question_versions} qv - ON q.id = qv.questionid - JOIN {question_bank_entries} qbe - ON qbe.id = qv.questionbankentryid - JOIN {question_categories} qc - ON qc.id = qbe.questioncategoryid - JOIN {context} ctx ON ctx.id = qc.contextid - LEFT JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = 70 - JOIN {course} c ON (ctx.contextlevel = 50 AND c.id = ctx.instanceid) - OR (ctx.contextlevel = 70 AND c.id = cm.course) - WHERE cq.id = :questionid'; - $course = $DB->get_record_sql($sql, ['questionid' => $this->id()]); - return $course ? $course->id : 0; - } - } diff --git a/classes/capquiz_question_attempt.php b/classes/capquiz_question_attempt.php index 6db66f2..a4cbd12 100755 --- a/classes/capquiz_question_attempt.php +++ b/classes/capquiz_question_attempt.php @@ -14,304 +14,94 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class represeting a capquiz question attempt - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace mod_capquiz; -use question_bank; -use question_engine; -use question_state; -use question_usage_by_activity; -use stdClass; +use core\persistent; /** - * Class capquiz_question_attempt + * Question attempt. * * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquiz_question_attempt { - - /** @var stdClass $record */ - private stdClass $record; - - /** @var question_usage_by_activity $quba */ - private question_usage_by_activity $quba; - - /** - * Constructor. - * - * @param question_usage_by_activity $quba - * @param stdClass $record - */ - public function __construct(question_usage_by_activity $quba, stdClass $record) { - $this->record = $record; - $this->quba = $quba; - } - - /** - * Creates a new question attempt for a user - * - * @param capquiz_user $user - * @param capquiz_question $question - */ - public static function create_attempt(capquiz_user $user, capquiz_question $question): ?capquiz_question_attempt { - $quba = $user->question_usage(); - $questions = question_load_questions([$question->question_id()]); - $targetquestion = reset($questions); - if (!$targetquestion) { - return null; - } - $questiondefinition = question_bank::make_question($targetquestion); - $slot = $quba->add_question($questiondefinition); - $quba->start_question($slot); - question_engine::save_questions_usage_by_activity($quba); - return self::insert_attempt_entry($user, $question, $slot); - } +class capquiz_question_attempt extends persistent { - /** - * Returns the users currently active attempt - * - * @param capquiz_user $user - */ - public static function active_attempt(capquiz_user $user): ?capquiz_question_attempt { - global $DB; - $entry = $DB->get_record('capquiz_attempt', ['user_id' => $user->id(), 'reviewed' => false]); - if (empty($entry)) { - return null; - } - return new capquiz_question_attempt($user->question_usage(), $entry); - } + /** @var string The table name. */ + const TABLE = 'capquiz_attempt'; /** - * Loads a users attempt based on the user and attempt id + * Return the definition of the properties of this model. * - * @param capquiz_user $user - * @param int $attemptid - */ - public static function load_attempt(capquiz_user $user, int $attemptid): ?capquiz_question_attempt { - global $DB; - $entry = $DB->get_record('capquiz_attempt', ['id' => $attemptid, 'user_id' => $user->id()]); - if (empty($entry)) { - return null; - } - return new capquiz_question_attempt($user->question_usage(), $entry); + * @return array + */ + protected static function define_properties(): array { + return [ + 'slot' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'user_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'question_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'reviewed' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'answered' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'time_answered' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'time_reviewed' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'question_rating_id' => [ + 'type' => PARAM_INT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'question_prev_rating_id' => [ + 'type' => PARAM_INT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'prev_question_rating_id' => [ + 'type' => PARAM_INT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'prev_question_prev_rating_id' => [ + 'type' => PARAM_INT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'user_rating_id' => [ + 'type' => PARAM_INT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'user_prev_rating_id' => [ + 'type' => PARAM_INT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + ]; } - - /** - * Returns the users previous attempt - * - * @param capquiz_user $user - */ - public static function previous_attempt(capquiz_user $user): ?capquiz_question_attempt { - global $DB; - $sql = 'SELECT * - FROM {capquiz_attempt} - WHERE user_id = :userid - ORDER BY time_reviewed DESC - LIMIT 1'; - $attempt = $DB->get_record_sql($sql, ['userid' => $user->id()], MUST_EXIST); - return new capquiz_question_attempt($user->question_usage(), $attempt); - } - - /** - * Returns the users inactive attempts (Answered and reviewed) - * - * @param capquiz_user $user - * @return capquiz_question_attempt[] - */ - public static function inactive_attempts(capquiz_user $user): array { - global $DB; - $records = $DB->get_records('capquiz_attempt', [ - 'user_id' => $user->id(), - 'answered' => true, - 'reviewed' => true, - ]); - return array_map(function (stdClass $record) use ($user) { - return new capquiz_question_attempt($user->question_usage(), $record); - }, array_values($records)); - } - - /** - * Returns the attempts id - */ - public function id(): int { - return $this->record->id; - } - - /** - * Returns the id of the question - */ - public function question_id(): int { - return $this->record->question_id; - } - - /** - * Returns the slot of the question - */ - public function question_slot(): int { - return $this->record->slot; - } - - /** - * Returns true if the attempt has an answer - */ - public function is_answered(): bool { - return $this->record->answered; - } - - /** - * Returns true if the answer is correct - */ - public function is_correctly_answered(): bool { - if (!$this->is_answered()) { - return false; - } - $moodleattempt = $this->quba->get_question_attempt($this->question_slot()); - return $moodleattempt->get_state()->is_correct(); - } - - /** - * Returns the state of the question - */ - public function get_state(): question_state { - $moodleattempt = $this->quba->get_question_attempt($this->question_slot()); - return $moodleattempt->get_state(); - } - - /** - * Returns true if the attempt is reviewed - */ - public function is_reviewed(): bool { - return $this->record->reviewed; - } - - /** - * Returns true if the attempt is not reviewed - */ - public function is_pending(): bool { - return !$this->is_reviewed(); - } - - /** - * Checks if the question is valid - */ - public function is_question_valid(): bool { - global $DB; - $sql = 'SELECT cq.id - FROM {capquiz_attempt} ca - JOIN {capquiz_question} cq - ON ca.question_id = cq.id - WHERE ca.id = :attemptid'; - $result = $DB->get_record_sql($sql, ['attemptid' => $this->id()]); - return $result !== false; - } - - /** - * Deletes attempt from database - */ - public function delete(): void { - global $DB; - $DB->delete_records('capquiz_attempt', ['id' => $this->id()]); - } - - /** - * Marks attempt as answered - */ - public function mark_as_answered(): void { - global $DB; - $submitteddata = $this->quba->extract_responses($this->question_slot()); - $this->quba->process_action($this->question_slot(), $submitteddata); - $this->record->answered = true; - $this->record->time_answered = time(); - $this->quba->finish_question($this->question_slot(), time()); - question_engine::save_questions_usage_by_activity($this->quba); - $DB->update_record('capquiz_attempt', $this->record); - } - - /** - * Marks attempt as viewed - */ - public function mark_as_reviewed(): void { - global $DB; - $this->record->reviewed = true; - $this->record->time_reviewed = time(); - $DB->update_record('capquiz_attempt', $this->record); - } - - /** - * Sets current question rating - * - * @param capquiz_question_rating $rating - * @param bool $previous - */ - public function set_question_rating(capquiz_question_rating $rating, bool $previous = false): void { - global $DB; - if ($previous) { - $this->record->question_prev_rating_id = $rating->id(); - } else { - $this->record->question_rating_id = $rating->id(); - } - $DB->update_record('capquiz_attempt', $this->record); - } - - /** - * Sets rating for the previous rating - * - * @param capquiz_question_rating $rating - * @param bool $previous - */ - public function set_previous_question_rating(capquiz_question_rating $rating, bool $previous = false): void { - global $DB; - if ($previous) { - $this->record->prev_question_prev_rating_id = $rating->id(); - } else { - $this->record->prev_question_rating_id = $rating->id(); - } - $DB->update_record('capquiz_attempt', $this->record); - } - - /** - * Sets the user rating of the attempt - * - * @param capquiz_user_rating $rating - * @param bool $previous - */ - public function set_user_rating(capquiz_user_rating $rating, bool $previous = false): void { - global $DB; - if ($previous) { - $this->record->user_prev_rating_id = $rating->id(); - } else { - $this->record->user_rating_id = $rating->id(); - } - $DB->update_record('capquiz_attempt', $this->record); - } - - /** - * Inserts an attempt into the database - * - * @param capquiz_user $user - * @param capquiz_question $question - * @param int $slot - */ - private static function insert_attempt_entry(capquiz_user $user, capquiz_question $question, - int $slot): ?capquiz_question_attempt { - global $DB; - $record = new stdClass(); - $record->slot = $slot; - $record->user_id = $user->id(); - $record->question_id = $question->id(); - $DB->insert_record('capquiz_attempt', $record); - return self::active_attempt($user); - } - } diff --git a/classes/capquiz_question_engine.php b/classes/capquiz_question_engine.php deleted file mode 100755 index bbbbe91..0000000 --- a/classes/capquiz_question_engine.php +++ /dev/null @@ -1,228 +0,0 @@ -. - -/** - * This file defines a class represeting a capquiz question engine - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use question_usage_by_activity; - -/** - * Class capquiz_question_engine - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_question_engine { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var question_usage_by_activity $quba */ - private question_usage_by_activity $quba; - - /** @var capquiz_matchmaking_strategy_loader $matchmakingloader */ - private capquiz_matchmaking_strategy_loader $matchmakingloader; - - /** @var capquiz_rating_system_loader $ratingsystemloader */ - private capquiz_rating_system_loader $ratingsystemloader; - - /** - * Constructor. - * - * @param capquiz $capquiz - * @param question_usage_by_activity $quba - * @param capquiz_matchmaking_strategy_loader $strategyloader - * @param capquiz_rating_system_loader $ratingsystemloader - */ - public function __construct(capquiz $capquiz, - question_usage_by_activity $quba, - capquiz_matchmaking_strategy_loader $strategyloader, - capquiz_rating_system_loader $ratingsystemloader) { - $this->capquiz = $capquiz; - $this->quba = $quba; - $this->matchmakingloader = $strategyloader; - $this->ratingsystemloader = $ratingsystemloader; - } - - /** - * Checks if the user has finished their attempt - * - * @param capquiz_user $user - */ - public function user_is_completed(capquiz_user $user): bool { - if (capquiz_question_attempt::active_attempt($user)) { - return false; - } - if ($this->find_question_for_user($user)) { - return false; - } - return true; - } - - /** - * Gets an attempt for the user, returns a new one if there are no active attempts - * - * @param capquiz_user $user - */ - public function attempt_for_user(capquiz_user $user): ?capquiz_question_attempt { - $attempt = capquiz_question_attempt::active_attempt($user); - return $attempt !== null ? $attempt : $this->new_attempt_for_user($user); - } - - /** - * Calls attempt_for_user with the user parameter as the current user - */ - public function attempt_for_current_user(): ?capquiz_question_attempt { - return $this->attempt_for_user($this->capquiz->user()); - } - - /** - * Deletes attempt if it is invalid - * - * @param capquiz_user $user - */ - public function delete_invalid_attempt(capquiz_user $user): void { - $attempt = $this->attempt_for_user($user); - if ($attempt !== null && !$attempt->is_question_valid()) { - $attempt->delete(); - } - } - - /** - * Handles answer - * - * @param capquiz_user $user - * @param capquiz_question_attempt $attempt - */ - public function attempt_answered(capquiz_user $user, capquiz_question_attempt $attempt): void { - if (!$attempt->is_question_valid()) { - return; - } - $ratingsystem = $this->ratingsystemloader->rating_system(); - $attempt->mark_as_answered(); - $attempt->set_user_rating($user->get_capquiz_user_rating(), true); - $question = $this->capquiz->question_list()->question($attempt->question_id()); - if ($attempt->is_correctly_answered()) { - $ratingsystem->update_user_rating($user, $question, 1); - $this->set_new_highest_star_if_attained($user); - } else { - $ratingsystem->update_user_rating($user, $question, 0); - } - $attempt->set_user_rating($user->get_capquiz_user_rating()); - $previousattempt = capquiz_question_attempt::previous_attempt($user); - if ($previousattempt) { - $this->update_question_rating($previousattempt, $attempt); - } - } - - /** - * Sets a new "highest star" score if the new score is the highest score yet - * - * @param capquiz_user $user - */ - private function set_new_highest_star_if_attained(capquiz_user $user): void { - $qlist = $this->capquiz->question_list(); - for ($star = $qlist->max_stars(); $star > 0; $star--) { - $required = $qlist->star_rating($star); - if ($user->rating() >= $required && $user->highest_stars_achieved() < $star) { - $user->set_highest_star($star); - break; - } - } - } - - /** - * Marks attempt as reviewed - * - * @param capquiz_question_attempt $attempt - */ - public function attempt_reviewed(capquiz_question_attempt $attempt): void { - $attempt->mark_as_reviewed(); - } - - /** - * Creates a new attempt for the user - * - * @param capquiz_user $user - */ - private function new_attempt_for_user(capquiz_user $user): ?capquiz_question_attempt { - $question = $this->find_question_for_user($user); - if ($question === null) { - return null; - } - return capquiz_question_attempt::create_attempt($user, $question); - } - - /** - * Finds a new question for the user - * - * @param capquiz_user $user - */ - private function find_question_for_user(capquiz_user $user): ?capquiz_question { - $selector = $this->matchmakingloader->selector(); - if ($selector === null) { - return null; - } - $questionlist = $this->capquiz->question_list(); - $inactiveattempts = capquiz_question_attempt::inactive_attempts($user); - return $selector->next_question_for_user($user, $questionlist, $inactiveattempts); - } - - /** - * Updates the question ratings - * - * @param capquiz_question_attempt $previous - * @param capquiz_question_attempt $current - */ - private function update_question_rating(capquiz_question_attempt $previous, capquiz_question_attempt $current): void { - $ratingsystem = $this->ratingsystemloader->rating_system(); - $currentcorrect = $current->is_correctly_answered(); - $previouscorrect = $previous->is_correctly_answered(); - $qlist = $this->capquiz->question_list(); - $currentquestion = $qlist->question($current->question_id()); - if (!$currentquestion) { - return; - } - $previousquestion = $qlist->question($previous->question_id()); - if (!$previousquestion) { - return; - } - $current->set_previous_question_rating($previousquestion->get_capquiz_question_rating(), true); - $current->set_question_rating($currentquestion->get_capquiz_question_rating(), true); - if ($previouscorrect && !$currentcorrect) { - $ratingsystem->question_victory_ratings($currentquestion, $previousquestion); - } else if (!$previouscorrect && $currentcorrect) { - $ratingsystem->question_victory_ratings($previousquestion, $currentquestion); - } else { - $previousquestion->set_rating($previousquestion->rating()); - $currentquestion->set_rating($currentquestion->rating()); - } - - $current->set_previous_question_rating($previousquestion->get_capquiz_question_rating()); - $current->set_question_rating($currentquestion->get_capquiz_question_rating()); - } - -} diff --git a/classes/capquiz_question_list.php b/classes/capquiz_question_list.php index 20e2d43..91c5b36 100755 --- a/classes/capquiz_question_list.php +++ b/classes/capquiz_question_list.php @@ -14,397 +14,61 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines the class capquiz_question_list - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace mod_capquiz; -use context_course; -use stdClass; +use core\persistent; /** - * Class capquiz_question_list + * Question list. * * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquiz_question_list { - - /** @var stdClass $record */ - private stdClass $record; - - /** @var capquiz_question[] $questions */ - private array $questions; - - /** - * Constructor. - * - * @param stdClass $record - */ - public function __construct(stdClass $record) { - global $DB; - $this->record = $record; - $records = $DB->get_records('capquiz_question', ['question_list_id' => $this->record->id]); - $this->questions = array_map(fn(stdClass $question) => new capquiz_question($question), array_values($records)); - } - - /** - * Returns array of ratings - * - * @return int[] - */ - public function star_ratings_array(): array { - $ratings = explode(',', $this->record->star_ratings); - return array_map(fn(string $rating) => (float)$rating, $ratings); - } - - /** - * Returns the count of all ratings (The value needed for a full star score) - */ - public function max_stars(): int { - return count($this->star_ratings_array()); - } - - /** - * Returns the value of the star rating $star - * - * @param int $star - */ - public function star_rating(int $star): float { - $stars = $this->star_ratings_array(); - return $stars[$star - 1]; - } - - /** - * Sets the star ratings to new values and updates database - * - * @param int[] $ratings - */ - public function set_star_ratings(array $ratings): void { - global $DB; - $starratings = implode(',', $ratings); - if (strlen($starratings) < 250) { - $this->record->star_ratings = $starratings; - $DB->update_record('capquiz_question_list', $this->record); - } - } - - /** - * Returns the completion level to the next rating as a percent value - * - * @param capquiz $capquiz - * @param float $rating - */ - public function next_level_percent(capquiz $capquiz, float $rating): int { - $goal = 0; - for ($star = 1; $star <= $this->max_stars(); $star++) { - $goal = $this->star_rating($star); - if ($goal > $rating) { - $previous = $star > 1 ? $this->star_rating($star - 1) : $capquiz->default_user_rating(); - $rating -= $previous; - $goal -= $previous; - break; - } - } - return $goal >= 1 ? (int)($rating / $goal * 100) : 0; - } - - /** - * Returns the id - */ - public function id(): int { - return $this->record->id; - } - - /** - * Returns the author user - */ - public function author(): ?stdClass { - global $DB; - return $DB->get_record('user', ['id' => $this->record->author]) ?: null; - } - - /** - * Returns true if the list contains questions - */ - public function has_questions(): bool { - return count($this->questions) > 0; - } - - /** - * Returns true if the question list is a template - */ - public function is_template(): bool { - return $this->record->is_template; - } - - /** - * Returns the default question rating - */ - public function default_question_rating(): float { - return $this->record->default_question_rating; - } - - /** - * Sets teh default question rating - * - * @param float $rating - */ - public function set_default_question_rating(float $rating): void { - global $DB; - $this->record->default_question_rating = $rating; - $DB->update_record('capquiz_question_list', $this->record); - } - - /** - * Returns the title of the question list - */ - public function title(): string { - return $this->record->title; - } - - /** - * Returns the description of the question list - */ - public function description(): string { - return $this->record->description; - } - - /** - * Returns the time of when the list was created - */ - public function time_created(): string { - return $this->record->time_created; - } - - /** - * Returns the last time when the list was modified - */ - public function time_modified(): string { - return $this->record->time_modified; - } - - /** - * Returns the amount of questions in the list - */ - public function question_count(): int { - return count($this->questions); - } - - /** - * Returns all the questions in the list in an array - * - * @return capquiz_question[] - */ - public function questions(): array { - return $this->questions; - } - - /** - * Returns the question with the id of $questionid - * - * @param int $questionid - */ - public function question(int $questionid): ?capquiz_question { - foreach ($this->questions as $question) { - if ($question->id() === $questionid) { - return $question; - } - } - return null; - } - - /** - * Checks if the list has the question with questionid $questionid - * - * @param int $questionid - * @return mixed|capquiz_question|null - */ - public function has_question(int $questionid): mixed { - foreach ($this->questions as $question) { - if ($question->question_id() === $questionid) { - return $question; - } - } - return null; - } - - /** - * The questions from $that will be imported to this question list. - * - * @param capquiz_question_list $that The question list to import questions from. - */ - public function merge(capquiz_question_list $that): void { - global $DB; - foreach ($that->questions as $question) { - if ($this->has_question($question->question_id()) === null) { - $newquestion = new stdClass(); - $newquestion->question_list_id = $this->id(); - $newquestion->question_id = $question->question_id(); - $newquestion->rating = $question->rating(); - $capquizquestionid = $DB->insert_record('capquiz_question', $newquestion, true); - capquiz_question_rating::insert_question_rating_entry($capquizquestionid, $newquestion->rating); - } - } - } - - /** - * Creates a copy of this instance - * - * @param capquiz $capquiz - */ - public function create_instance_copy(capquiz $capquiz): ?capquiz_question_list { - return $this->create_copy($capquiz, false); - } - - /** - * Updates database record - * - * @param int $capquizid - */ - public function convert_to_instance(int $capquizid): bool { - global $DB; - if ($this->id() || !$this->is_template()) { - return false; - } - $this->record->capquiz_id = $capquizid; - $this->record->is_template = 0; - $DB->update_record('capquiz_question_list', $this->record); - return true; +class capquiz_question_list extends persistent { + + /** @var string The table name. */ + const TABLE = 'capquiz_question_list'; + + /** + * Return the definition of the properties of this model. + * + * @return array + */ + protected static function define_properties(): array { + return [ + 'id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'capquiz_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_ALLOWED, + ], + 'title' => [ + 'type' => PARAM_TEXT, + 'null' => NULL_NOT_ALLOWED, + ], + 'author' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'description' => [ + 'type' => PARAM_TEXT, + 'null' => NULL_NOT_ALLOWED, + ], + 'is_template' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'context_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_ALLOWED, + ], + ]; } - - /** - * Creates a copy of this instance as template - * - * @param capquiz $capquiz - */ - public function create_template_copy(capquiz $capquiz): ?capquiz_question_list { - return $this->create_copy($capquiz, true); - } - - /** - * Copies the questions in this list to database - * - * @param int $qlistid - */ - private function copy_questions_to_list(int $qlistid): void { - global $DB; - foreach ($this->questions() as $question) { - $record = $question->entry(); - $record->id = null; - $record->question_list_id = $qlistid; - $capquizquestionid = $DB->insert_record('capquiz_question', $record); - capquiz_question_rating::insert_question_rating_entry($capquizquestionid, $record->rating); - } - } - - /** - * Creates a copy of this instance and inserts the new copy into the database - * - * @param capquiz $capquiz - * @param bool $template - * @return ?capquiz_question_list The new but identical (apart from identicators) question list instance - */ - private function create_copy(capquiz $capquiz, bool $template): ?capquiz_question_list { - global $DB; - $record = $this->record; - $record->id = null; - $record->capquiz_id = $template ? null : $capquiz->id(); - $record->context_id = context_course::instance($capquiz->course()->id)->id; - $record->is_template = $template; - $record->time_created = time(); - $record->time_modified = time(); - $transaction = $DB->start_delegated_transaction(); - try { - $newid = $DB->insert_record('capquiz_question_list', $record); - $this->copy_questions_to_list($newid); - $DB->commit_delegated_transaction($transaction); - $record->id = $newid; - return new capquiz_question_list($record); - } catch (\dml_exception $exception) { - $DB->rollback_delegated_transaction($transaction, $exception); - } - } - - /** - * Create new question list instance and insert it in database - * - * @param capquiz $capquiz - * @param string $title - * @param string $description - * @param array $ratings - */ - public static function create_new_instance(capquiz $capquiz, string $title, string $description, - array $ratings): ?capquiz_question_list { - global $DB, $USER; - if (count($ratings) < 5) { - return null; - } - $record = new stdClass(); - $record->capquiz_id = $capquiz->id(); - $record->title = $title; - $record->description = $description; - $record->star_ratings = implode(',', $ratings); - $record->author = $USER->id; - $record->is_template = 0; - $record->time_created = time(); - $record->time_modified = time(); - $record->context_id = context_course::instance($capquiz->course()->id)->id; - $qlistid = $DB->insert_record('capquiz_question_list', $record); - $qlist = self::load_any($qlistid); - if (!$qlist) { - return null; - } - $capquiz->validate_matchmaking_and_rating_systems(); - return $qlist; - } - - /** - * Loads question list from database based on the capquiz - * - * @param capquiz $capquiz - */ - public static function load_question_list(capquiz $capquiz): ?capquiz_question_list { - global $DB; - $record = $DB->get_record('capquiz_question_list', ['capquiz_id' => $capquiz->id()]); - return $record ? new capquiz_question_list($record) : null; - } - - /** - * Loads question list from database based on the question list id - * - * @param int $qlistid - */ - public static function load_any(int $qlistid): ?capquiz_question_list { - global $DB; - $record = $DB->get_record('capquiz_question_list', ['id' => $qlistid]); - return $record ? new capquiz_question_list($record) : null; - } - - /** - * Loads question list templates - * - * @return capquiz_question_list[] - * @throws \dml_exception - */ - public static function load_question_list_templates(): array { - global $DB; - $records = $DB->get_records('capquiz_question_list', ['is_template' => 1]); - $qlists = []; - foreach ($records as $record) { - $qlists[] = new capquiz_question_list($record); - } - return $qlists; - } - } diff --git a/classes/capquiz_question_rating.php b/classes/capquiz_question_rating.php index 80d2bca..e6eba31 100755 --- a/classes/capquiz_question_rating.php +++ b/classes/capquiz_question_rating.php @@ -14,135 +14,46 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class represeting a capquiz question rating - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace mod_capquiz; -use dml_exception; -use stdClass; +use core\persistent; /** * Class capquiz_question_rating * * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquiz_question_rating { - - /** @var stdClass $record */ - private stdClass $record; - - /** - * Constructor. - * - * @param stdClass $record - */ - public function __construct(stdClass $record) { - $this->record = $record; - } +class capquiz_question_rating extends persistent { - /** - * Loads a question rating from the database with a matching id - * - * @param int $questionratingid - */ - public static function load_question_rating(int $questionratingid): ?capquiz_question_rating { - global $DB; - $record = $DB->get_record('capquiz_question_rating', ['id' => $questionratingid]); - return empty($record) ? null : new capquiz_question_rating($record); - } + /** @var string The table name. */ + const TABLE = 'capquiz_question_rating'; /** - * Creates a new question rating and inserts it to the database + * Return the definition of the properties of this model. * - * @param capquiz_question $question - * @param float $rating - * @param bool $manual - */ - public static function create_question_rating(capquiz_question $question, float $rating, - bool $manual = false): capquiz_question_rating { - return self::insert_question_rating_entry($question->id(), $rating, $manual); - } - - /** - * Insert new question rating to database - * - * @param int $questionid - * @param float $rating - * @param bool $manual - */ - public static function insert_question_rating_entry(int $questionid, float $rating, - bool $manual = false): capquiz_question_rating { - global $DB; - $record = new stdClass(); - $record->capquiz_question_id = $questionid; - $record->rating = $rating; - $record->manual = $manual; - $record->timecreated = time(); - $ratingid = $DB->insert_record('capquiz_question_rating', $record); - $record->id = $ratingid; - return new capquiz_question_rating($record); - } - - /** - * Load information about the latest question rating for an attempt from the database. - * - * @param int $questionid - */ - public static function latest_question_rating_by_question(int $questionid): ?capquiz_question_rating { - global $DB; - $sql = "SELECT cqr.* - FROM {capquiz_question_rating} cqr - JOIN {capquiz_question} cq ON cq.id = cqr.capquiz_question_id - WHERE cqr.id = ( - SELECT MAX(cqr2.id) - FROM {capquiz_question_rating} cqr2 - JOIN {capquiz_question} cq2 ON cq2.id = cqr2.capquiz_question_id - WHERE cq2.id = cq.id - ) - AND cq.id = :question_id"; - $record = $DB->get_record_sql($sql, ['question_id' => $questionid]); - return empty($record) ? null : new capquiz_question_rating($record); - } - - /** - * Returns this question ratings id - */ - public function id(): int { - return $this->record->id; - } - - /** - * Returns the time of when the question rating was created - */ - public function timecreated(): string { - return $this->record->timecreated; - } - - /** - * Returns the question rating - */ - public function rating(): float { - return $this->record->rating; - } - - /** - * Sets the question rating - * - * @param float $rating - */ - public function set_rating(float $rating): void { - global $DB; - $this->record->rating = $rating; - $DB->update_record('capquiz_question_rating', $this->record); + * @return array + */ + protected static function define_properties(): array { + return [ + 'capquiz_question_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'rating' => [ + 'type' => PARAM_FLOAT, + 'default' => 0.0, + 'null' => NULL_NOT_ALLOWED, + ], + 'manual' => [ + 'type' => PARAM_BOOL, + 'default' => false, + 'null' => NULL_NOT_ALLOWED, + ], + ]; } } diff --git a/classes/capquiz_rating_system.php b/classes/capquiz_rating_system.php deleted file mode 100755 index baad384..0000000 --- a/classes/capquiz_rating_system.php +++ /dev/null @@ -1,75 +0,0 @@ -. - -/** - * This file defines an abstract capquiz rating system - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @author Aleksander Skrede - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use stdClass; - -/** - * Class capquiz_rating_system - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -abstract class capquiz_rating_system { - - /** - * Function to configure a rating system - * - * @param stdClass $config - */ - abstract public function configure(stdClass $config): void; - - /** - * Function to get the rating system configuration - */ - abstract public function configuration(): stdClass; - - /** - * Function to get the default rating system configuration - */ - abstract public function default_configuration(): stdClass; - - /** - * Updates the users rating based on the rating system and its configuration - * - * @param capquiz_user $user - * @param capquiz_question $question - * @param float $score - */ - abstract public function update_user_rating(capquiz_user $user, capquiz_question $question, float $score): void; - - /** - * Updates the winning and losing questions ratings - * - * @param capquiz_question $winner - * @param capquiz_question $loser - */ - abstract public function question_victory_ratings(capquiz_question $winner, capquiz_question $loser): void; - -} diff --git a/classes/capquiz_rating_system_loader.php b/classes/capquiz_rating_system_loader.php deleted file mode 100755 index 881fe94..0000000 --- a/classes/capquiz_rating_system_loader.php +++ /dev/null @@ -1,213 +0,0 @@ -. - -/** - * This file defines a class used to load capquiz rating systems - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use moodle_url; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/capquiz/classes/rating_system/capquiz_rating_system_registry.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/rating_system/elo_rating/elo_rating_system.php'); - -/** - * capquiz_rating_system_loader class - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_rating_system_loader { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var ?stdClass $record */ - private ?stdClass $record = null; - - /** @var capquiz_rating_system_registry $registry */ - private capquiz_rating_system_registry $registry; - - /** @var ?stdClass $configuration */ - private ?stdClass $configuration; - - /** - * Constructor. - * - * @param capquiz $capquiz - */ - public function __construct(capquiz $capquiz) { - $this->capquiz = $capquiz; - $this->registry = new capquiz_rating_system_registry(); - $this->load_configuration(); - } - - /** - * Returns rating system - */ - public function rating_system(): ?capquiz_rating_system { - if (!$this->record) { - return null; - } - $system = $this->registry->rating_system($this->record->rating_system); - if ($this->configuration) { - $system->configure($this->configuration); - } - return $system; - } - - /** - * Checks if this instance has a rating system - * - * @return bool - */ - public function has_rating_system(): bool { - return $this->rating_system() !== null; - } - - /** - * Returns configuration form - * - * @param moodle_url $url - */ - public function configuration_form(moodle_url $url): mixed { - if ($this->record && $this->configuration) { - return $this->registry->configuration_form($this->record->rating_system, $this->configuration, $url); - } - return null; - } - - /** - * Returns the current rating systems name - * - * @return string rating system name - */ - public function current_rating_system_name(): string { - if ($this->record) { - return $this->record->rating_system; - } - return 'No rating system specified'; - } - - /** - * Configure the current rating system. - * - * @param stdClass $candidateconfig - */ - public function configure_current_rating_system(stdClass $candidateconfig): void { - if (!$this->record) { - return; - } - $system = $this->rating_system(); - $system->configure($candidateconfig); - $config = $system->configuration(); - $this->record->configuration = empty((array)$config) ? '' : $this->serialize($config); - $this->update_configuration($this->record); - } - - /** - * Set the default rating system. - */ - public function set_default_rating_system(): void { - $this->set_rating_system($this->registry->default_rating_system()); - } - - /** - * Set the rating system - * - * @param string $ratingsystem - */ - public function set_rating_system(string $ratingsystem): void { - global $DB; - $system = $this->registry->rating_system($ratingsystem); - $record = new stdClass; - $record->rating_system = $ratingsystem; - $record->capquiz_id = $this->capquiz->id(); - $defaultconfig = $system->default_configuration(); - $record->configuration = empty((array)$defaultconfig) ? '' : $this->serialize($defaultconfig); - if ($this->record) { - $record->id = $this->record->id; - $this->update_configuration($record); - } else { - $DB->insert_record('capquiz_rating_system', $record); - $this->set_configuration($record); - } - } - - /** - * Loads this instances configuration - */ - private function load_configuration(): void { - global $DB; - $configuration = $DB->get_record('capquiz_rating_system', ['capquiz_id' => $this->capquiz->id()]); - if ($configuration) { - $this->set_configuration($configuration); - } - } - - /** - * Updates this instances configuration as well as updates the database - * - * @param stdClass $configuration - */ - private function update_configuration(stdClass $configuration): void { - global $DB; - if ($DB->update_record('capquiz_rating_system', $configuration)) { - $this->set_configuration($configuration); - } - } - - /** - * Sets this instances configuration - * - * @param stdClass $record - */ - private function set_configuration(stdClass $record): void { - $this->record = $record; - $this->configuration = $this->deserialize($record->configuration); - } - - /** - * Serializes the input configuration object - * - * @param stdClass $configuration the configuration to be serialized - * @return string json string representing the input configuration - */ - private function serialize(stdClass $configuration): string { - return json_encode($configuration); - } - - /** - * Deserializes JSON formatted configuration string - * - * @param string $configuration The JSON string to be deserialized back into a configuration object - */ - private function deserialize(string $configuration): mixed { - return json_decode($configuration, false); - } - -} diff --git a/classes/capquiz_urls.php b/classes/capquiz_urls.php deleted file mode 100755 index 85e100d..0000000 --- a/classes/capquiz_urls.php +++ /dev/null @@ -1,372 +0,0 @@ -. - -/** - * This file defines a class that represents a capquiz url - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @author André Storhaug - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use moodle_url; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/capquiz/report/reportlib.php'); - -/** - * Class capquiz_urls - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @author André Storhaug - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_urls { - - /** @var string The URL to the entrypoint view for the capquiz */ - public static string $urlview = '/mod/capquiz/view.php'; - - /** @var string The URL to update the user attempts and return to the dashboard */ - public static string $urlasync = '/mod/capquiz/async.php'; - - /** @var string The URL to the error page */ - public static string $urlerror = '/mod/capquiz/error.php'; - - /** @var string The URL to the action page */ - public static string $urlaction = '/mod/capquiz/action.php'; - - /** @var string The URL to the classlist view */ - public static string $urlviewclasslist = '/mod/capquiz/view_classlist.php'; - - /** @var string The URL to the grading view */ - public static string $urlviewgrading = '/mod/capquiz/view_grading.php'; - - /** @var string The URL to the import view */ - public static string $urlviewimport = '/mod/capquiz/view_import.php'; - - /** @var string The URL to the report view */ - public static string $urlviewreport = '/mod/capquiz/view_report.php'; - - /** @var string The URL for the capquiz editor */ - public static string $urledit = '/mod/capquiz/edit.php'; - - /** @var string The URL to the create question list view */ - public static string $urlviewcreateqlist = '/mod/capquiz/view_create_question_list.php'; - - /** @var string The URL to the rating system view */ - public static string $urlviewratingsystemconfig = '/mod/capquiz/view_rating_system.php'; - - /** - * Returns a redirect url - * - * @param moodle_url $target - */ - public static function redirect(moodle_url $target): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'redirect'); - $url->param('target-url', $target->out_as_local_url()); - return $url; - } - - /** - * Generates a url based on a relative url - * - * @param string $relativeurl - */ - public static function create_view_url(string $relativeurl): moodle_url { - global $CFG; - $url = new moodle_url($CFG->wwwroot . $relativeurl); - $url->param('id', self::require_course_module_id_param()); - return $url; - } - - /** - * Returns the course module id - */ - public static function require_course_module_id_param(): int { - $id = optional_param('id', 0, PARAM_INT); - if ($id !== 0) { - return $id; - } - return required_param('cmid', PARAM_INT); - } - - /** - * Redirects to the front page - */ - public static function redirect_to_front_page(): void { - global $CFG; - redirect(new moodle_url($CFG->wwwroot)); - } - - /** - * Redirects to the dashboard - */ - public static function redirect_to_dashboard(): void { - self::redirect_to_url(self::create_view_url(self::$urlview)); - } - - /** - * Redirects to specified url - * - * @param moodle_url $url - */ - public static function redirect_to_url(moodle_url $url): void { - redirect($url); - } - - /** - * Redirects to the previous page - */ - public static function redirect_to_previous(): void { - header('Location: ' . $_SERVER['HTTP_REFERER']); - exit; - } - - /** - * Sets teh current page url - * - * @param capquiz $capquiz - * @param string $url - */ - public static function set_page_url(capquiz $capquiz, string $url): void { - global $PAGE; - $PAGE->set_context($capquiz->context()); - $PAGE->set_cm($capquiz->course_module()); - $PAGE->set_pagelayout('incourse'); - $PAGE->set_url(self::create_view_url($url)); - } - - /** - * Returns url to the front page of the capquiz dashboard - */ - public static function view_url(): moodle_url { - return self::create_view_url(self::$urlview); - } - - /** - * Returns the url to the question list view - * - * @param int $questionpage - */ - public static function view_question_list_url(int $questionpage = 0): moodle_url { - $url = self::create_view_url(self::$urledit); - $url->param('qpage', $questionpage); - return $url; - } - - /** - * Returns the url to the rating system view - */ - public static function view_rating_system_url(): moodle_url { - return self::create_view_url(self::$urlviewratingsystemconfig); - } - - /** - * Returns the url to the grading view - */ - public static function view_grading_url(): moodle_url { - return self::create_view_url(self::$urlviewgrading); - } - - /** - * Returns the url to the classlist/leaderboard view - */ - public static function view_classlist_url(): moodle_url { - return self::create_view_url(self::$urlviewclasslist); - } - - /** - * Returns url to the "create question list" view - */ - public static function view_create_question_list_url(): moodle_url { - return self::create_view_url(self::$urlviewcreateqlist); - } - - /** - * Returns url to the import view - */ - public static function view_import_url(): moodle_url { - return self::create_view_url(self::$urlviewimport); - } - - /** - * Returns url to the report view - * - * @param string $mode - */ - public static function view_report_url(string $mode = ''): moodle_url { - return self::report_url(self::$urlviewreport, $mode); - } - - /** - * Generates and returns url to the report view - * - * @param string $relativeurl - * @param string $mode - */ - public static function report_url(string $relativeurl, string $mode): moodle_url { - $url = self::create_view_url($relativeurl); - if ($mode !== '') { - $url->param('mode', $mode); - } - return $url; - } - - /** - * Generates and returns url to add a qyestion to the list with - * the parameters to add question to the list - * - * @param int $questionid - */ - public static function add_question_to_list_url(int $questionid): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'add-question'); - $url->param('question-id', $questionid); - return $url; - } - - /** - * Generates and returns url to remove a question from a list with - * the parameters to remove question from the list - * - * @param int $questionid - */ - public static function remove_question_from_list_url(int $questionid): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'remove-question'); - $url->param('question-id', $questionid); - return $url; - } - - /** - * Generates and returns url to publish a question list with - * the parameters to publish the question list - * - * @param capquiz_question_list $qlist - */ - public static function question_list_publish_url(capquiz_question_list $qlist): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'publish-question-list'); - $url->param('question-list-id', $qlist->id()); - return $url; - } - - /** - * Generates and returns url to create a question list template with - * the parameters to create the template - * - * @param capquiz_question_list $qlist - */ - public static function question_list_create_template_url(capquiz_question_list $qlist): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'create-question-list-template'); - $url->param('question-list-id', $qlist->id()); - return $url; - } - - /** - * Generates and returns url to select a question list with - * the parameters to set the question list - * - * @param capquiz_question_list $qlist - */ - public static function question_list_select_url(capquiz_question_list $qlist): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'set-question-list'); - $url->param('question-list-id', $qlist->id()); - return $url; - } - - /** - * Generates and returns url to set a question rating with - * the parameters to set the question rating - * - * @param int $questionid - */ - public static function set_question_rating_url(int $questionid): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'set-question-rating'); - $url->param('question-id', $questionid); - return $url; - } - - /** - * Generates and returns url to regrade all - */ - public static function regrade_all_url(): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'regrade-all'); - return $url; - } - - /** - * Generates and returns url to merge qlist with the parameters to merge the qlist - * - * @param int $qlistid - */ - public static function merge_qlist(int $qlistid): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'merge_qlist'); - $url->param('qlistid', $qlistid); - return $url; - } - - /** - * Generates and returns url to delete a question list with the parameters to delete the list - * - * @param int $qlistid - */ - public static function delete_qlist(int $qlistid): moodle_url { - $url = self::create_view_url(self::$urlaction); - $url->param('action', 'delete_qlist'); - $url->param('qlistid', $qlistid); - return $url; - } - - /** - * Generates and returns url to submit an attempt - * - * @param capquiz_question_attempt $attempt - */ - public static function response_submit_url(capquiz_question_attempt $attempt): moodle_url { - $url = self::create_view_url(self::$urlasync); - $url->param('action', 'answered'); - $url->param('attempt', $attempt->id()); - return $url; - } - - /** - * Generates and returns url to mark an attempt as reviewed - * - * @param capquiz_question_attempt $attempt - */ - public static function response_reviewed_url(capquiz_question_attempt $attempt): moodle_url { - $url = self::create_view_url(self::$urlasync); - $url->param('action', 'reviewed'); - $url->param('attempt', $attempt->id()); - return $url; - } -} diff --git a/classes/capquiz_user.php b/classes/capquiz_user.php index 043aa32..af7bb0b 100755 --- a/classes/capquiz_user.php +++ b/classes/capquiz_user.php @@ -14,240 +14,60 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class representing a capquiz user - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace mod_capquiz; -use context_module; -use question_engine; -use question_usage_by_activity; -use stdClass; +use core\persistent; /** * capquiz_user class * * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquiz_user { - - /** @var stdClass $record */ - private stdClass $record; - - /** @var stdClass $user */ - private stdClass $user; - - /** @var capquiz_user_rating $rating */ - private capquiz_user_rating $rating; - - /** @var question_usage_by_activity $quba */ - private question_usage_by_activity $quba; - - /** - * Constructor. - * - * @param stdClass $record - * @param context_module $context - */ - public function __construct(stdClass $record, context_module $context) { - global $DB; - $this->record = $record; - $this->user = $DB->get_record('user', ['id' => $this->record->user_id]); - - $rating = capquiz_user_rating::latest_user_rating_by_user($record->id); - if ($rating === null) { - $this->rating = capquiz_user_rating::insert_user_rating_entry($this->id(), $this->rating()); - } else { - $this->rating = $rating; - } - $this->create_question_usage($context); - $this->quba = question_engine::load_questions_usage_by_activity($this->record->question_usage_id); - } - - /** - * Verify if user has permission to use question - */ - private function has_question_usage(): bool { - return $this->record->question_usage_id !== null; - } - - /** - * Create question usage - * - * @param context_module $context - */ - public function create_question_usage(context_module $context): void { - global $DB; - if ($this->has_question_usage()) { - return; - } - $quba = question_engine::make_questions_usage_by_activity('mod_capquiz', $context); - $quba->set_preferred_behaviour('immediatefeedback'); - // TODO: Don't suppress the error if it becomes possible to save QUBAs without slots. - @question_engine::save_questions_usage_by_activity($quba); - $this->record->question_usage_id = $quba->get_id(); - $DB->update_record('capquiz_user', $this->record); - } - - - /** - * Return this user's quba. - */ - public function question_usage(): ?question_usage_by_activity { - return $this->quba; - } - - /** - * Loads capquiz user - * - * @param capquiz $capquiz - * @param int $moodleuserid - * @param context_module $context - */ - public static function load_user(capquiz $capquiz, int $moodleuserid, context_module $context): ?capquiz_user { - global $DB; - if ($user = self::load_db_entry($capquiz, $moodleuserid, $context)) { - return $user; - } - $record = new stdClass(); - $record->user_id = $moodleuserid; - $record->capquiz_id = $capquiz->id(); - $record->rating = $capquiz->default_user_rating(); - $capquizuserid = $DB->insert_record('capquiz_user', $record); - capquiz_user_rating::insert_user_rating_entry($capquizuserid, $record->rating); - return self::load_db_entry($capquiz, $moodleuserid, $context); - } +class capquiz_user extends persistent { - /** - * Returns count of users in this capquiz - * - * @param int $capquizid - * @return int count of users in this capquiz - */ - public static function user_count(int $capquizid): int { - global $DB; - return $DB->count_records('capquiz_user', ['capquiz_id' => $capquizid]); - } + /** @var string The table name. */ + const TABLE = 'capquiz_user'; /** - * Returns list of all users in this capquiz + * Return the definition of the properties of this model. * - * @param int $capquizid - * @param context_module $context - * @return capquiz_user[] - */ - public static function list_users(int $capquizid, context_module $context): array { - global $DB; - $records = $DB->get_records('capquiz_user', ['capquiz_id' => $capquizid]); - return array_map(fn(stdClass $record) => new capquiz_user($record, $context), array_values($records)); + * @return array + */ + protected static function define_properties(): array { + return [ + 'user_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'capquiz_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'question_usage_id' => [ + 'type' => PARAM_INT, + 'default' => null, + 'null' => NULL_ALLOWED, + ], + 'rating' => [ + 'type' => PARAM_FLOAT, + 'default' => 0.0, + 'null' => NULL_NOT_ALLOWED, + ], + 'highest_level' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + 'stars_graded' => [ + 'type' => PARAM_INT, + 'default' => 0, + 'null' => NULL_NOT_ALLOWED, + ], + ]; } - - /** - * Return this user's id - */ - public function id(): int { - return $this->record->id; - } - - /** - * Returns this user's username - */ - public function username(): string { - return $this->user->username; - } - - /** - * Returns this user's first name - */ - public function first_name(): string { - return $this->user->firstname; - } - - /** - * Returns this user's last name - */ - public function last_name(): string { - return $this->user->lastname; - } - - /** - * Return users rating - */ - public function rating(): float { - return $this->record->rating; - } - - /** - * Get this user's capquiz rating - */ - public function get_capquiz_user_rating(): capquiz_user_rating { - return $this->rating; - } - - /** - * Return the highest star rating this user has achieved - */ - public function highest_stars_achieved(): int { - return $this->record->highest_level; - } - - /** - * Return the highest star grade - */ - public function highest_stars_graded(): int { - return $this->record->stars_graded; - } - - /** - * Set this user's highest star rating - * - * @param int $higheststar - */ - public function set_highest_star(int $higheststar): void { - global $DB; - $this->record->highest_level = $higheststar; - $DB->update_record('capquiz_user', $this->record); - } - - /** - * Set this user's rating - * - * @param float $rating - * @param bool $manual - */ - public function set_rating(float $rating, bool $manual = false): void { - global $DB; - $this->record->rating = $rating; - $DB->update_record('capquiz_user', $this->record); - $userrating = capquiz_user_rating::create_user_rating($this, $rating, $manual); - $this->rating = $userrating; - } - - /** - * Load user entry from database - * - * @param capquiz $capquiz - * @param int $moodleuserid - * @param context_module $context - */ - private static function load_db_entry(capquiz $capquiz, int $moodleuserid, context_module $context): ?capquiz_user { - global $DB; - $entry = $DB->get_record('capquiz_user', [ - 'user_id' => $moodleuserid, - 'capquiz_id' => $capquiz->id(), - ]); - return empty($entry) ? null : new capquiz_user($entry, $context); - } - } diff --git a/classes/capquiz_user_rating.php b/classes/capquiz_user_rating.php index 5bfcd1f..a4b8dd5 100755 --- a/classes/capquiz_user_rating.php +++ b/classes/capquiz_user_rating.php @@ -14,129 +14,46 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class represeting a capquiz user rating - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace mod_capquiz; -use stdClass; +use core\persistent; /** - * Class capquiz_user_rating + * User rating. * * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquiz_user_rating { - - /** @var stdClass $record */ - private stdClass $record; - - /** - * Constructor. - * - * @param stdClass $record - */ - public function __construct(stdClass $record) { - $this->record = $record; - } - - /** - * Loads and returns user rating from database - * - * @param int $questionratingid - */ - public static function load_user_rating(int $questionratingid): ?capquiz_user_rating { - global $DB; - $record = $DB->get_record('capquiz_question_rating', ['id' => $questionratingid]); - if ($record === false) { - return null; - } - return new capquiz_user_rating($record); - } - - /** - * Creates and inserts a new user rating to the database - * - * @param capquiz_user $user - * @param float $rating - * @param bool $manual - */ - public static function create_user_rating(capquiz_user $user, float $rating, bool $manual = false): ?capquiz_user_rating { - return self::insert_user_rating_entry($user->id(), $rating, $manual); - } - - /** - * Load information about the latest user rating for an capquiz user from the database. - * - * @param int $capquizuserid - */ - public static function latest_user_rating_by_user(int $capquizuserid): ?capquiz_user_rating { - global $DB; - $sql = "SELECT cur.* - FROM {capquiz_user_rating} cur - JOIN {capquiz_user} cu ON cu.id = cur.capquiz_user_id - WHERE cur.id = ( - SELECT MAX(cur2.id) - FROM {capquiz_user_rating} cur2 - JOIN {capquiz_user} cu2 ON cu2.id = cur2.capquiz_user_id - WHERE cu2.id = cu.id - ) - AND cu.id = :capquiz_user_id"; - $record = $DB->get_record_sql($sql, ['capquiz_user_id' => $capquizuserid]); - - return $record ? new capquiz_user_rating($record) : null; - } - - /** - * Inserts a new user rating record to the database - * - * @param int $capquizuserid - * @param float $rating - * @param bool $manual - */ - public static function insert_user_rating_entry(int $capquizuserid, float $rating, bool $manual = false): capquiz_user_rating { - global $DB, $USER; - $record = new stdClass(); - $record->capquiz_user_id = $capquizuserid; - $record->rating = $rating; - $record->manual = $manual; - $record->timecreated = time(); - $record->user_id = $USER->id; - $record->id = $DB->insert_record('capquiz_user_rating', $record); - return new capquiz_user_rating($record); - } - - /** - * Returns this user ratings id - */ - public function id(): int { - return $this->record->id; - } +class capquiz_user_rating extends persistent { - /** - * Returns this user ratings rating - */ - public function rating(): float { - return $this->record->rating; - } + /** @var string The table name. */ + const TABLE = 'capquiz_user_rating'; /** - * Sets this user ratings rating and updates the database record + * Return the definition of the properties of this model. * - * @param float $rating - */ - public function set_rating(float $rating): void { - global $DB; - $this->record->rating = $rating; - $DB->update_record('capquiz_user_rating', $this->record); + * @return array + */ + protected static function define_properties(): array { + return [ + 'capquiz_user_id' => [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + ], + 'rating' => [ + 'type' => PARAM_FLOAT, + 'default' => 0.0, + 'null' => NULL_NOT_ALLOWED, + ], + 'manual' => [ + 'type' => PARAM_BOOL, + 'default' => false, + 'null' => NULL_NOT_ALLOWED, + ], + ]; } } diff --git a/classes/external.php b/classes/external.php new file mode 100644 index 0000000..15a174c --- /dev/null +++ b/classes/external.php @@ -0,0 +1,118 @@ +. + +declare(strict_types=1); + +namespace mod_capquiz; + +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_value; +use external_api; + +/** + * External API. + * + * @package mod_capquiz + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class external extends external_api { + + /** + * Parameters for set_question_rating + * + * @return external_function_parameters + */ + public static function set_question_rating_parameters(): external_function_parameters { + return new external_function_parameters([ + 'questionid' => new external_value(PARAM_INT, 'question id', VALUE_REQUIRED, null, NULL_NOT_ALLOWED), + 'rating' => new external_value(PARAM_FLOAT, 'question rating', VALUE_REQUIRED, null, NULL_NOT_ALLOWED), + ]); + } + + /** + * Set question rating. + * + * @param int $questionid + * @param float $rating + * @return array + */ + public static function set_question_rating(int $questionid, float $rating): array { + [ + 'questionid' => $questionid, + 'rating' => $rating, + ] = self::validate_parameters(self::set_question_rating_parameters(), [ + 'questionid' => $questionid, + 'rating' => $rating, + ]); + api::set_question_rating($questionid, $rating, true); + return ['result' => true]; + } + + /** + * Return for set_question_rating + * + * @return external_single_structure + */ + public static function set_question_rating_returns(): external_single_structure { + return new external_single_structure(['result' => new external_value(PARAM_BOOL)]); + } + + /** + * Parameters for add_question_to_list + * + * @return external_function_parameters + */ + public static function add_question_to_list_parameters(): external_function_parameters { + return new external_function_parameters([ + 'qlistid' => new external_value(PARAM_INT, 'question list id', VALUE_REQUIRED, null, NULL_NOT_ALLOWED), + 'questionid' => new external_value(PARAM_INT, 'question id', VALUE_REQUIRED, null, NULL_NOT_ALLOWED), + ]); + } + + /** + * Set question rating. + * + * @param int $qlistid + * @param int $questionid + * @return array + */ + public static function add_question_to_list(int $qlistid, int $questionid): array { + [ + 'qlistid' => $qlistid, + 'questionid' => $questionid, + ] = self::validate_parameters(self::set_question_rating_parameters(), [ + 'qlistid' => $qlistid, + 'questionid' => $questionid, + ]); + $qlist = new capquiz_question_list($qlistid); + $capquiz = new capquiz($qlist->get('capquiz_id')); + api::add_question_to_list($qlist, $questionid, $capquiz->get('defaultquestionrating')); + return ['result' => true]; + } + + /** + * Return for add_question_to_list + * + * @return external_single_structure + */ + public static function add_question_to_list_returns(): external_single_structure { + return new external_single_structure(['result' => new external_value(PARAM_BOOL)]); + } + +} diff --git a/classes/form/view/grading_configuration_form.php b/classes/form/view/grading_configuration_form.php deleted file mode 100644 index 65852fe..0000000 --- a/classes/form/view/grading_configuration_form.php +++ /dev/null @@ -1,121 +0,0 @@ -. - -/** - * CAPQuiz grading configuration form definition. - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\form\view; - -use mod_capquiz\capquiz; -use moodle_url; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * grading_configuration_form class - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class grading_configuration_form extends \moodleform { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** - * Constructor. - * - * @param capquiz $capquiz - * @param moodle_url $url - */ - public function __construct(capquiz $capquiz, moodle_url $url) { - $this->capquiz = $capquiz; - parent::__construct($url); - } - - /** - * Defines form - */ - public function definition(): void { - $qlist = $this->capquiz->question_list(); - $form = $this->_form; - $form->addElement('text', 'default_user_rating', get_string('default_user_rating', 'capquiz')); - $form->setType('default_user_rating', PARAM_INT); - $form->setDefault('default_user_rating', $this->capquiz->default_user_rating()); - $form->addRule('default_user_rating', get_string('default_user_rating_required', 'capquiz'), 'required', null, 'client'); - for ($star = 1; $star <= $qlist->max_stars(); $star++) { - $groupname = "star_group_$star"; - $input = "star_rating_$star"; - $text = get_string('level_rating', 'capquiz', $star); - $elements = []; - $elements[] = $form->createElement('text', $input, $text); - if ($star > 1) { - $elements[] = $form->createElement('submit', "delstarbutton$star", get_string('delete_star', 'capquiz')); - } - $form->addGroup($elements, $groupname, $text, [''], false); - $form->setType($input, PARAM_INT); - $form->setDefault($input, $qlist->star_rating($star)); - } - - $form->addElement('submit', 'addstarbutton', get_string('add_star', 'capquiz')); - - $strstarstopass = get_string('stars_to_pass', 'capquiz'); - $strstarstopassrequired = get_string('stars_to_pass_required', 'capquiz'); - $form->addElement('text', 'starstopass', $strstarstopass); - $form->setType('starstopass', PARAM_INT); - $form->setDefault('starstopass', $this->capquiz->stars_to_pass()); - $form->addRule('starstopass', $strstarstopassrequired, 'required', null, 'client'); - - $strduedate = get_string('due_time_grading', 'capquiz'); - $form->addElement('date_time_selector', 'timedue', $strduedate); - $form->setType('timedue', PARAM_INT); - $timedue = $this->capquiz->time_due(); - $oneweek = 60 * 60 * 24 * 7; - $form->setDefault('timedue', $timedue ? $timedue : time() + $oneweek); - - $form->addElement('submit', 'submitbutton', get_string('savechanges')); - } - - /** - * Validate the data from the form. - * - * @param array $data array of ("fieldname"=>value) of submitted data - * @param array $files array of uploaded files "element_name"=>tmp_file_path - * @return array of "element_name"=>"error_description" if there are errors, - * or an empty array if everything is OK (true allowed for backwards compatibility too). - */ - public function validations($data, $files): array { - $errors = []; - if (empty($data['default_user_rating'])) { - $errors['default_user_rating'] = get_string('default_user_rating_required', 'capquiz'); - } - if (empty($data['starstopass']) || $data['starstopass'] < 0 || $data['starstopass'] > 5) { - $errors['starstopass'] = get_string('stars_to_pass_required', 'capquiz'); - } - return $errors; - } - -} diff --git a/classes/form/view/matchmaking_strategy_selection_form.php b/classes/form/view/matchmaking_strategy_selection_form.php deleted file mode 100755 index 5b25326..0000000 --- a/classes/form/view/matchmaking_strategy_selection_form.php +++ /dev/null @@ -1,98 +0,0 @@ -. - -/** - * CAPQuiz matchmaking strategy selection form definition. - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\form\view; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_matchmaking_strategy_loader; -use mod_capquiz\capquiz_matchmaking_strategy_registry; -use moodle_url; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * matchmaking_strategy_selection form class - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class matchmaking_strategy_selection_form extends \moodleform { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** - * Constructor. - * - * @param capquiz $capquiz - * @param moodle_url $url - */ - public function __construct(capquiz $capquiz, moodle_url $url) { - $this->capquiz = $capquiz; - parent::__construct($url); - } - - /** - * Defines form - */ - public function definition(): void { - $form = $this->_form; - $loader = new capquiz_matchmaking_strategy_loader($this->capquiz); - $registry = new capquiz_matchmaking_strategy_registry($this->capquiz); - $strategies = $registry->selection_strategies(); - $index = 0; - $selectedindex = -1; - $radioarray = []; - foreach ($strategies as $strategy) { - if ($loader->current_strategy_name() === $strategy) { - $selectedindex = $index; - } - $localized = capquiz_matchmaking_strategy_loader::localized_strategy_name($strategy); - $radioarray[] = $form->createElement('radio', 'strategy', '', $localized, $index, [$strategy]); - $index++; - } - $form->addGroup($radioarray, 'radioar', '', '
', false); - $this->add_action_buttons(false); - if ($selectedindex > -1) { - $form->setDefault('strategy', $selectedindex); - } - } - - /** - * Validate the data from the form. - * - * @param array $data array of ("fieldname"=>value) of submitted data - * @param array $files array of uploaded files "element_name"=>tmp_file_path - * @return array of "element_name"=>"error_description" if there are errors, - * or an empty array if everything is OK (true allowed for backwards compatibility too). - */ - public function validations($data, $files): array { - return []; - } - -} diff --git a/classes/form/view/question_list_create_form.php b/classes/form/view/question_list_create_form.php deleted file mode 100755 index 63c5d78..0000000 --- a/classes/form/view/question_list_create_form.php +++ /dev/null @@ -1,95 +0,0 @@ -. - -/** - * CAPQuiz question list form definition. - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\form\view; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * question_list_create_form class - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class question_list_create_form extends \moodleform { - - /** - * Defines form - */ - public function definition(): void { - $form = $this->_form; - $form->addElement('text', 'title', get_string('title', 'capquiz')); - $form->setType('title', PARAM_TEXT); - $form->addRule('title', get_string('title_required', 'capquiz'), 'required', null, 'client'); - - $form->addElement('textarea', 'description', get_string('description', 'capquiz')); - $form->setType('description', PARAM_TEXT); - $form->addRule('description', get_string('description_required', 'capquiz'), 'required', null, 'client'); - - $ratings = [1300, 1450, 1600, 1800, 2000]; - for ($level = 1; $level < 6; $level++) { - $element = "level_{$level}_rating"; - $text = get_string('level_rating', 'capquiz', $level); - $requiredtext = get_string('level_rating_required', 'capquiz', $level); - $form->addElement('text', $element, $text); - $form->setType($element, PARAM_INT); - $form->addRule($element, $requiredtext, 'required', null, 'client'); - $form->setDefault($element, $ratings[$level - 1]); - } - - $form->addElement('submit', 'submitbutton', get_string('create_question_list', 'capquiz')); - } - - /** - * Validate the data from the form. - * - * @param array $data array of ("fieldname"=>value) of submitted data - * @param array $files array of uploaded files "element_name"=>tmp_file_path - * @return array of "element_name"=>"error_description" if there are errors, - * or an empty array if everything is OK (true allowed for backwards compatibility too). - */ - public function validations($data, $files): array { - $errors = []; - if (empty($data['title'])) { - $errors['title'] = get_string('title_required', 'capquiz'); - } - if (empty($data['description'])) { - $errors['description'] = get_string('description_required', 'capquiz'); - } - for ($level = 1; $level < 6; $level++) { - $element = "level_{$level}_rating"; - if (empty($data[$element])) { - $requiredtext = get_string('level_rating_required', 'capquiz', $level); - $errors[$element] = $requiredtext; - } - } - return $errors; - } - -} diff --git a/classes/form/view/rating_system_selection_form.php b/classes/form/view/rating_system_selection_form.php deleted file mode 100755 index c95401f..0000000 --- a/classes/form/view/rating_system_selection_form.php +++ /dev/null @@ -1,97 +0,0 @@ -. - -/** - * CAPQuiz rating system selection form definition. - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\form\view; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_rating_system_loader; -use mod_capquiz\capquiz_rating_system_registry; -use moodle_url; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * rating_system_selection_form class - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class rating_system_selection_form extends \moodleform { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** - * Constructor. - * - * @param capquiz $capquiz - * @param moodle_url $url - */ - public function __construct(capquiz $capquiz, moodle_url $url) { - $this->capquiz = $capquiz; - parent::__construct($url); - } - - /** - * Defines form - */ - public function definition(): void { - $form = $this->_form; - $loader = new capquiz_rating_system_loader($this->capquiz); - $registry = new capquiz_rating_system_registry(); - $index = 0; - $selectedindex = -1; - $radioarray = []; - foreach ($registry->rating_systems() as $ratingsystem) { - if ($loader->current_rating_system_name() === $ratingsystem) { - $selectedindex = $index; - } - $radioarray[] = $form->createElement('radio', 'rating_system', '', $ratingsystem, $index, [$ratingsystem]); - $index++; - } - $form->addGroup($radioarray, 'radioar', '', '
', false); - $this->add_action_buttons(false); - if ($selectedindex > -1) { - $form->setDefault('rating_system', $selectedindex); - } - } - - /** - * Validate the data from the form. - * - * @param array $data array of ("fieldname"=>value) of submitted data - * @param array $files array of uploaded files "element_name"=>tmp_file_path - * @return array of "element_name"=>"error_description" if there are errors, - * or an empty array if everything is OK (true allowed for backwards compatibility too). - */ - public function validations($data, $files): array { - return []; - } - -} diff --git a/report/attemptsreport_options.php b/classes/local/reports/options.php similarity index 67% rename from report/attemptsreport_options.php rename to classes/local/reports/options.php index 5451f1f..b332baf 100644 --- a/report/attemptsreport_options.php +++ b/classes/local/reports/options.php @@ -14,17 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Base class for the options that control what is visible in an {@see quiz_attempts_report}. - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); -namespace mod_capquiz\report; +namespace mod_capquiz\local\reports; +use cm_info; use mod_capquiz\capquiz; use moodle_url; use stdClass; @@ -34,55 +28,64 @@ require_once($CFG->libdir . '/formslib.php'); /** - * Base class for the options that control what is visible in an {@see quiz_attempts_report}. + * Base class for the options that control what is visible in a report. * * @author André Storhaug * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquiz_attempts_report_options { +class options { - /** @var string the report mode. */ - public $mode; + /** @var int default page size for reports. */ + const DEFAULT_PAGE_SIZE = 30; - /** @var object the settings for the capquiz being reported on. */ - public $capquiz; + /** @var string constant used for the options, means all users with attempts. */ + const ALL_WITH = 'all_with'; - /** @var object the course module objects for the capquiz being reported on. */ - public $cm; + /** @var string constant used for the options, means only enrolled users with attempts. */ + const ENROLLED_WITH = 'enrolled_with'; - /** @var object the course settings for the course the capquiz is in. */ - public $course; + /** @var string constant used for the options, means only enrolled users without attempts. */ + const ENROLLED_WITHOUT = 'enrolled_without'; - /** - * @var string capquiz_attempts_report::ALL_WITH or capquiz_attempts_report::ENROLLED_WITH - * capquiz_attempts_report::ENROLLED_WITHOUT or capquiz_attempts_report::ENROLLED_ALL - */ - public $attempts = capquiz_attempts_report::ENROLLED_WITH; + /** @var string constant used for the options, means all enrolled users. */ + const ENROLLED_ALL = 'enrolled_any'; - /** - * @var bool whether to show all attempts, or just the ones that are answered. - */ - public $onlyanswered = true; + /** @var string report type */ + public string $reporttype; + + /** @var capquiz the settings for the capquiz being reported on. */ + public capquiz $capquiz; + + /** @var cm_info the course module objects for the capquiz being reported on. */ + public cm_info $cm; + + /** @var stdClass the course settings for the course the capquiz is in. */ + public stdClass $course; + + /** @var string ALL_WITH, ENROLLED_WITH, ENROLLED_WITHOUT, or ENROLLED_ALL */ + public string $attempts = self::ENROLLED_WITH; + + /** @var bool whether to show all attempts, or just the ones that are answered. */ + public bool $onlyanswered = true; /** @var int Number of attempts to show per page. */ - public $pagesize = capquiz_attempts_report::DEFAULT_PAGE_SIZE; + public int $pagesize = self::DEFAULT_PAGE_SIZE; /** @var string whether the data should be downloaded in some format, or '' to display it. */ - public $download = ''; + public string $download = ''; /** @var bool whether the report table should have a column of checkboxes. */ - public $checkboxcolumn = false; + public bool $checkboxcolumn = false; /** * Constructor. - * @param string $mode which report these options are for. - * @param capquiz $capquiz the settings for the capquiz being reported on. - * @param object $cm the course module objects for the capquiz being reported on. - * @param object $course the course settings for the coures this capquiz is in. + * + * @param capquiz $capquiz + * @param cm_info $cm + * @param stdClass $course */ - public function __construct(string $mode, capquiz $capquiz, object $cm, object $course) { - $this->mode = $mode; + public function __construct(capquiz $capquiz, cm_info $cm, stdClass $course) { $this->capquiz = $capquiz; $this->cm = $cm; $this->course = $course; @@ -96,7 +99,7 @@ public function __construct(string $mode, capquiz $capquiz, object $cm, object $ protected function get_url_params(): array { return [ 'id' => $this->cm->id, - 'mode' => $this->mode, + 'reporttype' => $this->reporttype, 'attempts' => $this->attempts, 'onlyanswered' => $this->onlyanswered, ]; @@ -106,7 +109,7 @@ protected function get_url_params(): array { * Get the URL to show the report with these options. */ public function get_url(): moodle_url { - return new moodle_url('/mod/capquiz/view_report.php', $this->get_url_params()); + return new moodle_url('/mod/capquiz/report.php', $this->get_url_params()); } /** @@ -144,6 +147,7 @@ public function get_initial_form_data(): stdClass { /** * Set the fields of this object from the form data. + * * @param stdClass $fromform The data from $mform->get_data() from the settings form. */ public function setup_from_form_data(stdClass $fromform): void { @@ -157,7 +161,7 @@ public function setup_from_form_data(stdClass $fromform): void { */ public function setup_from_params(): void { $this->attempts = optional_param('attempts', $this->attempts, PARAM_ALPHAEXT); - $this->onlyanswered = optional_param('onlyanswered', $this->onlyanswered, PARAM_BOOL); + $this->onlyanswered = (bool)optional_param('onlyanswered', $this->onlyanswered, PARAM_BOOL); $this->pagesize = optional_param('pagesize', $this->pagesize, PARAM_INT); $this->download = optional_param('download', $this->download, PARAM_ALPHA); } @@ -167,7 +171,7 @@ public function setup_from_params(): void { * (For those settings that are backed by user-preferences). */ public function setup_from_user_preferences(): void { - $this->pagesize = get_user_preferences('capquiz_report_pagesize', $this->pagesize); + $this->pagesize = (int)get_user_preferences('capquiz_report_pagesize', $this->pagesize); } /** @@ -183,8 +187,7 @@ public function update_user_preferences(): void { */ public function resolve_dependencies(): void { if ($this->pagesize < 1) { - $this->pagesize = capquiz_attempts_report::DEFAULT_PAGE_SIZE; + $this->pagesize = self::DEFAULT_PAGE_SIZE; } } - } diff --git a/view_classlist.php b/classes/local/reports/report.php old mode 100755 new mode 100644 similarity index 51% rename from view_classlist.php rename to classes/local/reports/report.php index e28c451..1774703 --- a/view_classlist.php +++ b/classes/local/reports/report.php @@ -14,27 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Display leaderboard - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); -namespace mod_capquiz; +namespace mod_capquiz\local\reports; -require_once('../../config.php'); -require_once($CFG->dirroot . '/question/editlib.php'); -require_once($CFG->dirroot . '/mod/capquiz/lib.php'); +use cm_info; +use mod_capquiz\capquiz; +use stdClass; -$cmid = capquiz_urls::require_course_module_id_param(); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); -require_capability('mod/capquiz:instructor', $context); +defined('MOODLE_INTERNAL') || die(); -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urledit); -$capquiz->renderer()->display_leaderboard($capquiz); +/** + * Interface for CAPQuiz reports. + * + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface report { + /** + * Display report. + * + * @param capquiz $capquiz + * @param cm_info $cm + * @param stdClass $course + * @param string $download + */ + public function display(capquiz $capquiz, cm_info $cm, stdClass $course, string $download): void; +} diff --git a/report/attemptsreport_table.php b/classes/local/reports/table.php similarity index 66% rename from report/attemptsreport_table.php rename to classes/local/reports/table.php index f474ea4..54f942e 100644 --- a/report/attemptsreport_table.php +++ b/classes/local/reports/table.php @@ -14,23 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Base class for the table used by a {@see quiz_attempts_report}. - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); -namespace mod_capquiz\report; +namespace mod_capquiz\local\reports; use coding_exception; use core\context; -use core\context\module; use core\dml\sql_join; use html_writer; +use mod_capquiz\capquiz; use moodle_url; +use popup_action; use qubaid_condition; use qubaid_list; use question_engine_data_mapper; @@ -43,66 +37,64 @@ require_once($CFG->libdir . '/tablelib.php'); /** - * Base class for the table used by a {@see capquiz_attempts_report}. + * Base class for the table used by a {@see report}. * * @package mod_capquiz * @author André Storhaug * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -abstract class capquiz_attempts_report_table extends table_sql { - - /** @var string the name of the userid field */ - public $useridfield = 'userid'; +abstract class table extends table_sql { /** @var moodle_url the URL of this report. */ - protected $reporturl; + protected moodle_url $reporturl; /** @var array the display options. */ - protected $displayoptions; + protected array $displayoptions; /** - * @var array information about the latest step of each question. + * @var ?array information about the latest step of each question. * Loaded by {@see load_question_latest_steps()}, if applicable. */ - protected $lateststeps = null; + protected ?array $lateststeps = null; - /** @var object the capquiz settings for the capquiz we are reporting on. */ - protected $capquiz; + /** @var capquiz the capquiz settings for the capquiz we are reporting on. */ + protected capquiz $capquiz; /** @var context the capquiz context. */ - protected $context; + protected context $context; - /** @var object mod_quiz_attempts_report_options the options affecting this report. */ - protected $options; + /** @var options the options affecting this report. */ + protected options $options; /** @var sql_join Contains joins, wheres, params to find the students in the course. */ - protected $studentsjoins; + protected sql_join $studentsjoins; /** @var array the questions that comprise this capquiz.. */ - protected $questions; + protected array $questions; /** @var bool whether to include the column with checkboxes to select each attempt. */ - protected $includecheckboxes; + protected bool $includecheckboxes; /** @var string date format. */ - protected $strtimeformat; + protected string $strtimeformat; /** * Constructor. * * @param string $uniqueid - * @param object $quiz + * @param capquiz $capquiz * @param context $context - * @param capquiz_attempts_report_options $options + * @param options $options * @param sql_join $studentsjoins Contains joins, wheres, params * @param array $questions * @param moodle_url $reporturl */ - public function __construct($uniqueid, $quiz, context $context, capquiz_attempts_report_options $options, - sql_join $studentsjoins, $questions, $reporturl) { - parent::__construct($uniqueid); - $this->capquiz = $quiz; + public function __construct($uniqueid, capquiz $capquiz, context $context, options $options, + sql_join $studentsjoins, array $questions, moodle_url $reporturl) { + parent::__construct("capquiz_report_$uniqueid"); + $this->useridfield = 'userid'; + $this->capquiz = $capquiz; $this->context = $context; $this->studentsjoins = $studentsjoins; $this->questions = $questions; @@ -113,6 +105,7 @@ public function __construct($uniqueid, $quiz, context $context, capquiz_attempts /** * Generate the display of the checkbox column. + * * @param stdClass $attempt the table row being output. * @return string HTML content to go inside the td. */ @@ -124,24 +117,6 @@ public function col_checkbox(stdClass $attempt): string { } } - /** - * Generate the display of the user's full name column. - * - * @param object $attempt the table row being output. - */ - public function col_fullname($attempt): string { - $html = parent::col_fullname($attempt); - if ($this->is_downloading() || empty($attempt->attempt)) { - return $html; - } - return $html; - // phpcs:disable - /*. html_writer::empty_tag('br') . html_writer::link( - new moodle_url('/mod/capquiz/review.php', array('attempt' => $attempt->attempt)), - get_string('reviewattempt', 'quiz'), array('class' => 'reviewlink'))*/ - // phpcs:enable - } - /** * Generate the display of the time answered column. * @@ -156,11 +131,10 @@ public function col_timeanswered(stdClass $attempt): string { * * @param stdClass $attempt the table row being output. */ - public function col_timereviewed(stdClass $attempt) { + public function col_timereviewed(stdClass $attempt): string { return property_exists($attempt, 'attempt') ? userdate($attempt->timereviewed, $this->strtimeformat) : '-'; } - /** * Generate the display of the question id column. * @@ -192,48 +166,33 @@ public function col_userid(stdClass $attempt): string { * Make a link to review an individual question in a popup window. * * @param string $data HTML fragment. The text to make into the link. - * @param object $attempt data for the row of the table being output. + * @param stdClass $attempt data for the row of the table being output. * @param int $slot the number used to identify this question within this usage. */ - public function make_review_link($data, $attempt, $slot): string { + public function make_review_link(string $data, stdClass $attempt, int $slot): string { $feedbackimg = ''; $state = $this->slot_state($attempt, $slot); if ($state->is_finished() && $state != question_state::$needsgrading) { $feedbackimg = $this->icon_for_fraction($this->slot_fraction($attempt, $slot)); } - - $output = html_writer::tag('span', $feedbackimg . html_writer::tag('span', - $data, ['class' => $state->get_state_class(true)]), ['class' => 'que']); - - $reviewparams = ['attempt' => $attempt->attempt, 'slot' => $slot]; - if (isset($attempt->try)) { - $reviewparams['step'] = $this->step_no_for_try($attempt->usageid, $slot, $attempt->try); - } - - // TODO enable this when capquiz implements a "review question attempt" page. - // phpcs:disable - /*$url = new moodle_url('/mod/capquiz/reviewquestion.php', $reviewparams); - $output = $OUTPUT->action_link($url, $output, - new popup_action('click', $url, 'reviewquestion', array('height' => 450, 'width' => 650)), - array('title' => get_string('reviewresponse', 'quiz')));*/ - // phpcs:enable - return $output; + return html_writer::tag('span', + $feedbackimg . html_writer::tag('span', $data, ['class' => $state->get_state_class(true)]), ['class' => 'que']); } /** * Make a link to review an individual question in a popup window. * * @param string $data HTML fragment. The text to make into the link. - * @param object $attempt data for the row of the table being output. + * @param stdClass $attempt data for the row of the table being output. * @param int $slot the number used to identify this question within this usage. */ - public function make_preview_link($data, $attempt, $slot) { + public function make_preview_link(string $data, stdClass $attempt, int $slot) { global $OUTPUT; $questionid = $this->slot_questionid($attempt, $slot); $output = html_writer::tag('span', html_writer::tag('span', $data), ['class' => 'que']); $url = \qbank_previewquestion\helper::question_preview_url($questionid)->out(false); - return $OUTPUT->action_link($url, $output, new \popup_action('click', $url, 'previewquestion', [ - 'height' => 450, 'width' => 650]), ['title' => get_string('previewquestion', 'quiz')]); + $action = new popup_action('click', $url, 'previewquestion', ['height' => 450, 'width' => 650]); + return $OUTPUT->action_link($url, $output, $action, ['title' => get_string('previewquestion', 'quiz')]); } /** @@ -256,7 +215,7 @@ protected function slot_state(stdClass $attempt, int $slot): question_state { */ protected function slot_questionid(stdClass $attempt, int $slot): int { $stepdata = $this->lateststeps[$attempt->usageid][$slot]; - return $stepdata->questionid; + return (int)$stepdata->questionid; } /** @@ -304,20 +263,18 @@ public function setup_sql_queries(sql_join $allowedjoins): void { * @return array with 4 elements ($fields, $from, $where, $params) that can be used to * build the actual database query. */ - public function base_sql(sql_join $allowedstudentsjoins) { + public function base_sql(sql_join $allowedstudentsjoins): array { global $DB; - $fields = 'DISTINCT ' . $DB->sql_concat('u.id', "'#'", 'COALESCE(ca.id, 0)') . ' AS uniqueid,'; - $extrafields = \core_user\fields::for_identity($this->context) - ->including( - 'id', 'idnumber', 'firstname', 'lastname', 'picture', 'imagealt', 'institution', 'department', 'email' - ) + ->including('id', 'idnumber', 'firstname', 'lastname', 'picture', 'imagealt', 'institution', 'department', 'email') + ->get_sql('u')->selects; + + $allnames = \core_user\fields::for_name() + ->with_identity($this->context) ->get_sql('u')->selects; - // phpcs:disable - // $allnames = get_all_user_name_fields(true, 'u'); - // phpcs:enable - $allnames = \core_user\fields::for_name()->with_identity($this->context)->get_sql('u')->selects; + + $fields = 'DISTINCT ' . $DB->sql_concat('u.id', "'#'", 'COALESCE(ca.id, 0)') . ' AS uniqueid,'; $fields .= ' cu.question_usage_id AS usageid, ca.id AS attempt, @@ -333,66 +290,61 @@ public function base_sql(sql_join $allowedstudentsjoins) { ca.time_reviewed AS timereviewed'; // This part is the same for all cases. Join the users and capquiz_attempts tables. - $from = " {user} u"; - $from .= "\nJOIN {capquiz_user} cu ON u.id = cu.user_id"; - $from .= "\nLEFT JOIN {capquiz_question_list} cql - ON cql.capquiz_id = :capquizid - AND cql.is_template = 0"; + $from = " {user} u + JOIN {capquiz_user} cu ON u.id = cu.user_id + LEFT JOIN {capquiz_question_list} cql ON cql.capquiz_id = :capquizid AND cql.is_template = 0 - $from .= "\nJOIN {question_usages} qu ON qu.id = cu.question_usage_id"; - $from .= "\nJOIN {question_attempts} qa ON qa.questionusageid = qu.id"; + JOIN {question_usages} qu ON qu.id = cu.question_usage_id + JOIN {question_attempts} qa ON qa.questionusageid = qu.id - $from .= "\nJOIN {capquiz_attempt} ca ON ca.user_id = cu.id AND ca.slot = qa.slot"; - $from .= "\nJOIN {capquiz_question} cq ON cq.question_list_id = cql.id AND cq.id = ca.question_id"; + JOIN {capquiz_attempt} ca ON ca.user_id = cu.id AND ca.slot = qa.slot + JOIN {capquiz_question} cq ON cq.question_list_id = cql.id AND cq.id = ca.question_id"; - $params = ['capquizid' => $this->capquiz->id()]; + $params = ['capquizid' => $this->capquiz->get('id')]; switch ($this->options->attempts) { - case capquiz_attempts_report::ALL_WITH: + case options::ALL_WITH: // Show all attempts, including students who are no longer in the course. $where = 'ca.id IS NOT NULL'; break; - case capquiz_attempts_report::ENROLLED_WITH: + case options::ENROLLED_WITH: // Show only students with attempts. $from .= "\n" . $allowedstudentsjoins->joins; $where = "ca.id IS NOT NULL AND " . $allowedstudentsjoins->wheres; $params = array_merge($params, $allowedstudentsjoins->params); break; - // phpcs:disable - /* - case capquiz_attempts_report::ENROLLED_WITHOUT: + case options::ENROLLED_WITHOUT: // Show only students without attempts. $from .= "\n" . $allowedstudentsjoins->joins; $where = "ca.id IS NULL AND " . $allowedstudentsjoins->wheres; $params = array_merge($params, $allowedstudentsjoins->params); break; - case capquiz_attempts_report::ENROLLED_ALL: + case options::ENROLLED_ALL: // Show all students with or without attempts. $from .= "\n" . $allowedstudentsjoins->joins; $where = $allowedstudentsjoins->wheres; $params = array_merge($params, $allowedstudentsjoins->params); break; - */ - // phpcs:enable + default: + return [$fields, $from, '', $params]; } if ($this->options->onlyanswered) { $where .= " AND ca.answered = 1"; } - return [$fields, $from, $where, $params]; } /** - * A chance for subclasses to modify the SQL after the count query has been generated, - * and before the full query is constructed. + * A chance for subclasses to modify the SQL after the count query is generated, and before the full query is constructed. + * * @param string $fields SELECT list. * @param string $from JOINs part of the SQL. * @param string $where WHERE clauses. * @param array $params Query params. * @return array with 4 elements ($fields, $from, $where, $params) as from base_sql. */ - protected function update_sql_after_count($fields, $from, $where, $params) { + protected function update_sql_after_count(string $fields, string $from, string $where, array $params): array { return [$fields, $from, $where, $params]; } @@ -400,32 +352,21 @@ protected function update_sql_after_count($fields, $from, $where, $params) { * Query the db. Store results in the table object for use by build_table. * * @param int $pagesize size of page for paginated displayed table. - * @param bool $useinitialsbar do you want to use the initials bar. Bar - * will only be used if there is a fullname column defined for the table. + * @param bool $useinitialsbar do you want to use the initials bar (only used if there is a fullname column) */ public function query_db($pagesize, $useinitialsbar = true): void { parent::query_db($pagesize, $useinitialsbar); - if ($this->requires_extra_data()) { + if ($this->requires_latest_steps_loaded()) { $this->load_extra_data(); } } - /** - * Does this report require loading any more data after the main query. After the main query then - * you can use $this->get - * - * @return bool should {@see query_db()} call {@see load_extra_data}? - */ - protected function requires_extra_data() { - return $this->requires_latest_steps_loaded(); - } - /** * Does this report require the detailed information for each question from the question_attempts_steps table? * * @return bool should {@see load_extra_data} call {@see load_question_latest_steps}? */ - protected function requires_latest_steps_loaded() { + protected function requires_latest_steps_loaded(): bool { return false; } @@ -449,10 +390,9 @@ protected function load_question_latest_steps(?qubaid_condition $qubaids = null) if ($qubaids === null) { $qubaids = $this->get_qubaids_condition(); } - $dm = new question_engine_data_mapper(); - $latesstepdata = $dm->load_questions_usages_latest_steps($qubaids, array_map(fn($o) => $o->slot, $this->questions)); $lateststeps = []; - foreach ($latesstepdata as $step) { + $dm = new question_engine_data_mapper(); + foreach ($dm->load_questions_usages_latest_steps($qubaids, array_map(fn($o) => $o->slot, $this->questions)) as $step) { $lateststeps[$step->questionusageid][$step->slot] = $step; } return $lateststeps; @@ -482,8 +422,8 @@ protected function get_qubaids_condition(): qubaid_list { * @return array column name => SORT_... constant. */ public function get_sort_columns(): array { - // Add attemptid as a final tie-break to the sort. This ensures that - // Attempts by the same student appear in order when just sorting by name. + // Add attemptid as a final tie-break to the sort. + // This ensures that attempts by the same student appear in order when just sorting by name. $sortcolumns = parent::get_sort_columns(); $sortcolumns['attempt'] = SORT_ASC; return $sortcolumns; @@ -492,7 +432,7 @@ public function get_sort_columns(): array { /** * Wrap start of table */ - public function wrap_html_start() { + public function wrap_html_start(): void { if ($this->is_downloading() || !$this->includecheckboxes) { return; } @@ -505,16 +445,13 @@ public function wrap_html_start() { } /** - * End of table wrap - * - * @throws coding_exception + * End of table wrap. */ - public function wrap_html_finish() { + public function wrap_html_finish(): void { global $PAGE; if ($this->is_downloading() || !$this->includecheckboxes) { return; } - echo '
'; echo '' . get_string('selectall', 'quiz') . ' / '; echo '' . get_string('selectnone', 'quiz') . ' '; @@ -530,39 +467,8 @@ public function wrap_html_finish() { }); });"); echo '  '; - - // TODO enable when support for attempt deletion is added {@see delete_selected_attempts}. - // phpcs:disable - // $this->submit_buttons(); - // phpcs:enable echo '
'; - - // Close the form. echo ''; echo ''; } - - /** - * Is this a column that depends on joining to the latest state information? - * If so, return the corresponding slot. If not, return false. - * @param string $column a column name - * @return int false if no, else a slot. - */ - protected function is_latest_step_column($column) { - return false; - } - - /** - * Output any submit buttons required by the $this->includecheckboxes form. - */ - protected function submit_buttons() { - global $PAGE; - if (has_capability('mod/capquiz:deleteattempts', $this->context)) { - echo ''; - $PAGE->requires->event_handler('#deleteattemptsbutton', 'click', 'M.util.show_confirm_dialog', - ['message' => get_string('deleteattemptcheck', 'quiz')]); - } - } - } diff --git a/classes/matchmaking/capquiz_matchmaking_strategy_registry.php b/classes/matchmaking/capquiz_matchmaking_strategy_registry.php deleted file mode 100755 index 1ec206e..0000000 --- a/classes/matchmaking/capquiz_matchmaking_strategy_registry.php +++ /dev/null @@ -1,155 +0,0 @@ -. - -/** - * This file defines a class acting as a registry for matchmaking strategies - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use coding_exception; -use moodle_url; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/capquiz/classes/capquiz_matchmaking_strategy.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/matchmaking/chronologic/chronologic_selector.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/matchmaking/n_closest/n_closest_selector.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/matchmaking/n_closest/n_closest_configuration_form.php'); - -/** - * Class capquiz_matchmaking_strategy_registry - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_matchmaking_strategy_registry { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var array $strategies */ - private array $strategies; - - /** - * Constructor. - * - * @param capquiz $capquiz - */ - public function __construct(capquiz $capquiz) { - $this->capquiz = $capquiz; - $this->register_selection_strategies(); - } - - /** - * Returns the specified matchmaking strategy or throws an error if it does not exist - * - * @param string $strategy - */ - public function selector(string $strategy): capquiz_matchmaking_strategy { - $value = $this->strategies[$strategy]; - if ($value) { - return array_values($value)[0](); - } - $this->throw_strategy_exception($strategy); - } - - /** - * Returns a configuration form for the matchmaking strategy - * - * @param string $strategy - * @param stdClass $config - * @param moodle_url $url - */ - public function configuration_form(string $strategy, stdClass $config, moodle_url $url) { - $value = $this->strategies[$strategy]; - if ($value) { - $configfunc = array_values($value)[1]; - return $configfunc($url, $config); - } - $this->throw_strategy_exception($strategy); - } - - /** - * Returns true if the registry has the specified strategy - * - * @param string $strategy - */ - public function has_strategy(string $strategy): bool { - return isset($this->strategies[$strategy]); - } - - /** - * Returns the default selection strategy - */ - public function default_selection_strategy(): string { - // The default selection strategy is added first. - // Modify capquiz_matchmaking_strategy_registry::register_selection_strategies() to change this. - $selectionstrategies = $this->selection_strategies(); - return reset($selectionstrategies); - } - - /** - * Returns all selection strategies' names - * - * @return string[] - */ - public function selection_strategies(): array { - $names = []; - foreach (array_keys($this->strategies) as $value) { - $names[] = $value; - } - return $names; - } - - /** - * Registers the selection strategies, the first registered will be the default strategy - */ - private function register_selection_strategies(): void { - // The first listed will be selected by default when creating a new activity. - $capquiz = $this->capquiz; - $this->strategies = [ - 'N-closest' => [ - fn() => new n_closest_selector($capquiz), - fn(moodle_url $url, stdClass $config) => new n_closest_configuration_form($config, $url), - ], - 'Chronological' => [ - fn() => new chronologic_selector(), - fn(moodle_url $url, stdClass $config) => null, - ], - ]; - } - - /** - * Creates and throws a strategy exception - * - * @param string $strategy - */ - private function throw_strategy_exception(string $strategy) { - $msg = "The specified strategy '$strategy' does not exist."; - $msg .= " Options are {'" . implode("', '", $this->selection_strategies()); - $msg .= "'}. This issue must be fixed by a programmer"; - throw new coding_exception($msg); - } -} diff --git a/classes/matchmaking/chronologic/chronologic_selector.php b/classes/matchmaking/chronologic/chronologic_selector.php deleted file mode 100755 index f09e45a..0000000 --- a/classes/matchmaking/chronologic/chronologic_selector.php +++ /dev/null @@ -1,86 +0,0 @@ -. - -/** - * This file defines a class which acts as a selector for the chronologic matchmaking strategy - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use stdClass; - -/** - * Class chronologic_selector - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class chronologic_selector extends capquiz_matchmaking_strategy { - - /** - * Nothing to configure - * - * @param stdClass $config - */ - public function configure(stdClass $config): void { - } - - /** - * No configuration needed - */ - public function configuration(): stdClass { - return new stdClass(); - } - - /** - * No configuration needed - */ - public function default_configuration(): stdClass { - return new stdClass(); - } - - /** - * Returns the next question for the user in a chronological order - * - * @param capquiz_user $user - * @param capquiz_question_list $qlist - * @param capquiz_question_attempt[] $inactiveattempts - */ - public function next_question_for_user(capquiz_user $user, capquiz_question_list $qlist, - array $inactiveattempts): ?capquiz_question { - $answered = function (capquiz_question $q) use ($inactiveattempts) { - foreach ($inactiveattempts as $inactiveattempt) { - if ($inactiveattempt->question_id() === $q->id()) { - return true; - } - } - return false; - }; - foreach ($qlist->questions() as $question) { - if (!$answered($question)) { - return $question; - } - } - return null; - } -} diff --git a/classes/matchmaking/n_closest/n_closest_configuration_form.php b/classes/matchmaking/n_closest/n_closest_configuration_form.php deleted file mode 100755 index 6d91a37..0000000 --- a/classes/matchmaking/n_closest/n_closest_configuration_form.php +++ /dev/null @@ -1,111 +0,0 @@ -. - -/** - * This file defines the configuration form for the n_closest matchmaking strategy - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use moodle_url; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * Class n_closest_configuration_form - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class n_closest_configuration_form extends \moodleform { - - /** @var stdClass $configuration */ - private stdClass $configuration; - - /** - * Constructor. - * - * @param stdClass $configuration - * @param moodle_url $url - */ - public function __construct(stdClass $configuration, moodle_url $url) { - $this->configuration = $configuration; - parent::__construct($url); - } - - /** - * Defines form - */ - public function definition(): void { - $form = $this->_form; - - $form->addElement('text', 'number_of_questions_to_select', get_string('number_of_questions_to_select', 'capquiz')); - $form->setType('number_of_questions_to_select', PARAM_INT); - $form->setDefault('number_of_questions_to_select', $this->configuration->number_of_questions_to_select); - $form->addRule('number_of_questions_to_select', get_string('number_of_questions_to_select_required', 'capquiz'), - 'required', null, 'client'); - $form->addHelpButton('number_of_questions_to_select', 'number_of_questions_to_select', 'capquiz'); - - $form->addElement('text', 'user_win_probability', get_string('user_win_probability', 'capquiz')); - $form->setType('user_win_probability', PARAM_FLOAT); - $form->setDefault('user_win_probability', $this->configuration->user_win_probability); - $form->addRule('user_win_probability', get_string('user_win_probability_required', 'capquiz'), - 'required', null, 'client'); - $form->addHelpButton('user_win_probability', 'user_win_probability', 'capquiz'); - - $form->addElement('text', 'prevent_same_question_for_turns', get_string('prevent_question_n_times', 'capquiz')); - $form->setType('prevent_same_question_for_turns', PARAM_INT); - $form->setDefault('prevent_same_question_for_turns', $this->configuration->prevent_same_question_for_turns); - $form->addRule('prevent_same_question_for_turns', get_string('field_required', 'capquiz'), - 'required', null, 'client'); - $form->addHelpButton('prevent_same_question_for_turns', 'prevent_question_n_times', 'capquiz'); - - $this->add_action_buttons(false); - } - - /** - * Validate the data from the form. - * - * @param array $data array of ("fieldname"=>value) of submitted data - * @param array $files array of uploaded files "element_name"=>tmp_file_path - * @return array of "element_name"=>"error_description" if there are errors, - * or an empty array if everything is OK (true allowed for backwards compatibility too). - */ - public function validations($data, $files): array { - $errors = []; - if (empty($data['user_win_probability'])) { - $errors['user_win_probability'] = get_string('user_win_probability_required', 'capquiz'); - } - if (empty($data['number_of_questions'])) { - $errors['number_of_questions'] = get_string('number_of_questions_to_select_required', 'capquiz'); - } - if (empty($data['prevent_same_question_for_turns'])) { - $errors['prevent_same_question_for_turns'] = get_string('field_required', 'capquiz'); - } - return $errors; - } - -} diff --git a/classes/matchmaking/n_closest/n_closest_selector.php b/classes/matchmaking/n_closest/n_closest_selector.php deleted file mode 100755 index 4a2221a..0000000 --- a/classes/matchmaking/n_closest/n_closest_selector.php +++ /dev/null @@ -1,174 +0,0 @@ -. - -/** - * This file defines a class which acts as a selector for the n_closest matchmaking strategy - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use stdClass; - -/** - * Class n_closest_selector - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class n_closest_selector extends capquiz_matchmaking_strategy { - - /** @var capquiz The capquiz */ - private capquiz $capquiz; - - /** @var float The propability of the user winning */ - private float $userwinprobability; - - /** @var int The number of questions to select */ - private int $numquestionstoselect; - - /** @var int The number of turns between each time a question can be selected */ - private int $preventsamequestionforturns; - - /** - * Constructor. - * - * @param capquiz $capquiz - */ - public function __construct(capquiz $capquiz) { - $this->capquiz = $capquiz; - $this->configure($this->default_configuration()); - } - - /** - * Configure the strategy - * - * @param stdClass $configuration - */ - public function configure(stdClass $configuration): void { - if ($configuration->user_win_probability > 0) { - $this->userwinprobability = $configuration->user_win_probability; - } - if ($configuration->number_of_questions_to_select > 0) { - $this->numquestionstoselect = $configuration->number_of_questions_to_select; - } - if ($configuration->prevent_same_question_for_turns >= 0) { - $this->preventsamequestionforturns = $configuration->prevent_same_question_for_turns; - } - } - - /** - * Returns the current strategy configuration - */ - public function configuration(): stdClass { - $config = new stdClass; - $config->prevent_same_question_for_turns = $this->preventsamequestionforturns; - $config->user_win_probability = $this->userwinprobability; - $config->number_of_questions_to_select = $this->numquestionstoselect; - return $config; - } - - /** - * Returns the default strategy configuration - */ - public function default_configuration(): stdClass { - $config = new stdClass; - $config->user_win_probability = 0.75; - $config->prevent_same_question_for_turns = 0; - $config->number_of_questions_to_select = 10; - return $config; - } - - /** - * Selects the next question for the user based on the configuration - * - * @param capquiz_user $user - * @param capquiz_question_list $qlist - * @param array $inactiveattempts - */ - public function next_question_for_user(capquiz_user $user, capquiz_question_list $qlist, - array $inactiveattempts): ?capquiz_question { - $excluded = $this->determine_excluded_questions($inactiveattempts); - $candidates = $this->find_questions_closest_to_rating($user, $excluded); - if (count($candidates) === 0) { - return null; - } - $index = mt_rand(0, count($candidates) - 1); - if ($question = $candidates[$index]) { - return $question; - } - return null; - } - - /** - * Finds the questions closest to the users rating - * - * @param capquiz_user $user - * @param array $excludedquestions - */ - private function find_questions_closest_to_rating(capquiz_user $user, array $excludedquestions): array { - global $DB; - $sql = 'SELECT * FROM {capquiz_question} WHERE question_list_id = ?'; - $sql .= str_repeat(' AND id <> ?', count($excludedquestions)); - $sql .= ' ORDER BY ABS(? - rating)'; - $params = []; - $params[] = $this->capquiz->question_list()->id(); - if (count($excludedquestions) > 0) { - array_push($params, ...$excludedquestions); - } - $params[] = $this->ideal_question_rating($user); - $questionentries = $DB->get_records_sql($sql, $params, 0, $this->numquestionstoselect); - $questions = []; - foreach ($questionentries as $questionentry) { - $questions[] = new capquiz_question($questionentry); - } - return $questions; - } - - /** - * Returns the ideal question rating - * - * @param capquiz_user $user - */ - private function ideal_question_rating(capquiz_user $user): float { - return 400.0 * log((1.0 / $this->userwinprobability) - 1.0, 10.0) + $user->rating(); - } - - /** - * Identifies questions to exclude and returns them in an array - * - * @param capquiz_question_attempt[] $inactiveattempts - */ - private function determine_excluded_questions(array $inactiveattempts): array { - $it = new \ArrayIterator(array_reverse($inactiveattempts, true)); - $excluded = []; - for ($i = 0; $i < $this->preventsamequestionforturns; $i++) { - if (!$it->valid()) { - break; - } - $excluded[] = $it->current()->question_id(); - $it->next(); - } - return array_unique($excluded); - } - -} diff --git a/classes/output/basic_renderer.php b/classes/output/basic_renderer.php deleted file mode 100755 index 2309030..0000000 --- a/classes/output/basic_renderer.php +++ /dev/null @@ -1,79 +0,0 @@ -. - -/** - * This file defines a class used to render buttons - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz_urls; -use moodle_url; -use renderer_base; - -/** - * Class basic_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class basic_renderer { - /** - * Renders the home button - * - * @param renderer_base $renderer - */ - public static function render_home_button(renderer_base $renderer): string { - $url = capquiz_urls::redirect(capquiz_urls::view_url()); - return self::render_action_button($renderer, $url, get_string('home', 'capquiz')); - } - - /** - * Renders a button - * - * @param renderer_base $renderer - * @param moodle_url $url - * @param string $label - * @param string $httpmethod The HTTP method to use for the form - * @param string[] $params The keys are used as names - * @param string $id - */ - public static function render_action_button(renderer_base $renderer, moodle_url $url, string $label, - string $httpmethod = 'post', array $params = [], string $id = ''): string { - $paramobjects = []; - foreach ($params as $name => $value) { - $paramobjects = [ - 'name' => $name, - 'value' => $value, - ]; - } - return $renderer->render_from_template('core/single_button', [ - 'type' => 'primary', - 'method' => $httpmethod, - 'url' => $url->out(false), - 'label' => $label, - 'params' => $paramobjects, - 'id' => $id, - ]); - } -} diff --git a/classes/output/classlist.php b/classes/output/classlist.php new file mode 100644 index 0000000..58c7e62 --- /dev/null +++ b/classes/output/classlist.php @@ -0,0 +1,95 @@ +. + +namespace mod_capquiz\output; + +use mod_capquiz\capquiz; +use moodle_url; +use renderable; +use renderer_base; +use templatable; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/question/editlib.php'); + +/** + * Render student leaderboard/classlist. + * + * @package mod_capquiz + * @author Aleksander Skrede + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class classlist implements renderable, templatable { + + /** @var capquiz $capquiz */ + private capquiz $capquiz; + + /** + * Constructor. + * + * @param capquiz $capquiz + */ + public function __construct(capquiz $capquiz) { + $this->capquiz = $capquiz; + } + + /** + * Render the classlist. + * + * @return bool|string + */ + public function render(renderer_base $output): bool|string { + return $output->render_from_template('capquiz/classlist', $this->export_for_template($output)); + } + + /** + * Export parameters for template. + * + * @param renderer_base $output + * @return array + */ + public function export_for_template(renderer_base $output): array { + global $DB, $PAGE; + $rows = []; + foreach ($DB->get_records('capquiz_user', ['capquiz_id' => $this->capquiz->get('id')]) as $user) { + $coreuser = $DB->get_record('user', ['id' => $user->user_id]); + $rows[] = [ + 'username' => $coreuser?->username, + 'firstname' => $coreuser?->firstname, + 'lastname' => $coreuser?->lastname, + 'rating' => round($user->rating, 2), + 'stars' => $user->highest_level, + 'graded_stars' => $user->stars_graded, + 'passing_grade' => $user->stars_graded >= $this->capquiz->get('stars_to_pass'), + ]; + } + $regradeallurl = new moodle_url('/mod/capquiz/action.php', ['id' => $PAGE->cm->id, 'action' => 'regrade-all']); + return [ + 'users' => $rows, + 'regrade' => [ + 'type' => 'primary', + 'method' => 'post', + 'classes' => 'capquiz-regrade-all', + 'url' => $regradeallurl->out(false), + 'label' => get_string('regrade_all', 'capquiz'), + 'disabled' => !$this->capquiz->is_grading_completed(), + ], + ]; + } +} diff --git a/classes/output/classlist_renderer.php b/classes/output/classlist_renderer.php deleted file mode 100755 index 17d600c..0000000 --- a/classes/output/classlist_renderer.php +++ /dev/null @@ -1,105 +0,0 @@ -. - -/** - * This file defines a class used to render a capquiz' classlist - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use mod_capquiz\capquiz_user; -use moodle_page; -use renderer_base; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/question/editlib.php'); - -/** - * Class classlist_renderer used for rendering a capquiz' class in the form of a list/leaderboard - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class classlist_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer_base $renderer */ - private renderer_base $renderer; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * Constructor. - * - * @param capquiz $capquiz The capquiz whose classlist should be rendered - * @param renderer_base $renderer The renderer used to render the classlist - */ - public function __construct(capquiz $capquiz, renderer_base $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - $this->page = $capquiz->get_page(); - } - - /** - * Renders the entire classlist of the $capquiz in the constructor - */ - public function render(): bool|string { - $cmid = $this->capquiz->course_module()->id; - $this->page->requires->js_call_amd('mod_capquiz/edit_questions', 'initialize', [$cmid]); - $users = capquiz_user::list_users($this->capquiz->id(), $this->capquiz->context()); - $rows = []; - for ($i = 0; $i < count($users); $i++) { - $user = $users[$i]; - $rows[] = [ - 'index' => $i + 1, - 'username' => $user->username(), - 'firstname' => $user->first_name(), - 'lastname' => $user->last_name(), - 'rating' => round($user->rating(), 2), - 'stars' => $user->highest_stars_achieved(), - 'graded_stars' => $user->highest_stars_graded(), - 'passing_grade' => $user->highest_stars_graded() >= $this->capquiz->stars_to_pass(), - ]; - } - return $this->renderer->render_from_template('capquiz/classlist', [ - 'users' => $rows, - 'regrade' => [ - 'type' => 'primary', - 'method' => 'post', - 'classes' => 'capquiz-regrade-all', - 'url' => capquiz_urls::regrade_all_url()->out(false), - 'label' => get_string('regrade_all', 'capquiz'), - 'disabled' => !$this->capquiz->is_grading_completed(), - ], - ]); - } - -} diff --git a/classes/output/grading_configuration_renderer.php b/classes/output/grading_configuration_renderer.php deleted file mode 100644 index 426669a..0000000 --- a/classes/output/grading_configuration_renderer.php +++ /dev/null @@ -1,121 +0,0 @@ -. - -/** - * This file defines a class used to render the grading configuration view - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use mod_capquiz\form\view\grading_configuration_form; -use moodle_page; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/question/editlib.php'); - -/** - * Class grading_configuration_renderer - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class grading_configuration_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer $renderer */ - private renderer $renderer; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * Constructor. - * - * @param capquiz $capquiz - * @param renderer $renderer - */ - public function __construct(capquiz $capquiz, renderer $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - $this->page = $capquiz->get_page(); - } - - /** - * Render grading configuration view - */ - public function render(): bool|string { - return $this->renderer->render_from_template('capquiz/configure_grading', [ - 'rating_form' => $this->get_rating_configuration(), - ]); - } - - /** - * Returns rating configuration form - */ - private function get_rating_configuration(): string { - $url = $this->page->url; - $form = new grading_configuration_form($this->capquiz, $url); - $formdata = $form->get_data(); - if ($formdata) { - $this->process_rating_configuration($formdata); - } - return $form->render(); - } - - /** - * Processes the rating configuration formdata - * - * @param stdClass $formdata - */ - private function process_rating_configuration(stdClass $formdata): void { - $star = 1; - $ratings = []; - while (isset($formdata->{"star_rating_$star"})) { - if (!isset($formdata->{"delstarbutton$star"})) { - $ratings[] = (int)$formdata->{"star_rating_$star"}; - } - $star++; - } - if (isset($formdata->addstarbutton)) { - $ratings[] = end($ratings) + 100; - } - if ($formdata->default_user_rating) { - $this->capquiz->set_default_user_rating($formdata->default_user_rating); - } - $this->capquiz->question_list()->set_star_ratings($ratings); - if ($formdata->starstopass) { - $this->capquiz->set_stars_to_pass($formdata->starstopass); - } - if ($formdata->timedue) { - $this->capquiz->set_time_due($formdata->timedue); - } - redirect(capquiz_urls::view_grading_url()); - } - -} diff --git a/classes/output/import_renderer.php b/classes/output/import_page.php similarity index 64% rename from classes/output/import_renderer.php rename to classes/output/import_page.php index 8a457b2..ef4e17c 100644 --- a/classes/output/import_renderer.php +++ b/classes/output/import_page.php @@ -14,47 +14,32 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class used to render the question list imports - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace mod_capquiz\output; use context_course; -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; +use moodle_url; use renderer_base; /** * Class import_renderer * * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU + * @author Sebastian Gundersen + * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class import_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; +class import_page implements \renderable, \templatable { - /** @var renderer_base $renderer */ - private renderer_base $renderer; + /** @var int $courseid */ + private int $courseid; /** * Constructor. * - * @param capquiz $capquiz The current capquiz - * @param renderer_base $renderer The renderer to be used by this instance + * @param int $courseid */ - public function __construct(capquiz $capquiz, renderer_base $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; + public function __construct(int $courseid) { + $this->courseid = $courseid; } /** @@ -81,49 +66,68 @@ private function get_questions_in_list(int $qlistid): array { */ private function get_question_lists(): array { global $DB; - $path = context_course::instance($this->capquiz->course()->id)->path; + $path = context_course::instance($this->courseid)->path; $sql = 'SELECT DISTINCT cql.* FROM {capquiz_question_list} cql JOIN {context} ctx ON (ctx.id = cql.context_id AND ctx.path LIKE :pathpart) OR cql.context_id IS NULL WHERE cql.is_template = 1 - ORDER BY cql.time_created DESC'; + ORDER BY cql.timecreated DESC'; return $DB->get_records_sql($sql, ['pathpart' => $path . '%']); } /** - * Render + * Render template. */ - public function render(): bool|string { - $srcqlists = $this->get_question_lists(); + public function render(renderer_base $output): bool|string { + return $output->render_from_template('capquiz/import_page', $this->export_for_template($output)); + } + + /** + * Export parameters for template. + * + * @param renderer_base $output + * @return array + */ + public function export_for_template(renderer_base $output): array { + global $PAGE; $qlists = []; - foreach ($srcqlists as $srcqlist) { + foreach ($this->get_question_lists() as $srcqlist) { $questions = []; foreach ($this->get_questions_in_list($srcqlist->id) as $question) { $questions[] = $question; } + $mergeurl = new moodle_url('/mod/capquiz/action.php', [ + 'id' => $PAGE->cm->id, + 'action' => 'merge_qlist', + 'qlistid' => $srcqlist->id, + ]); + $deleteurl = new moodle_url('/mod/capquiz/action.php', [ + 'id' => $PAGE->cm->id, + 'action' => 'delete_qlist', + 'qlistid' => $srcqlist->id, + ]); $qlists[] = [ 'title' => $srcqlist->title, - 'time_created' => $srcqlist->time_created, + 'timecreated' => $srcqlist->timecreated, 'description' => $srcqlist->description, 'questions' => $questions, 'merge' => [ 'type' => 'primary', 'method' => 'post', - 'url' => capquiz_urls::merge_qlist($srcqlist->id)->out(false), + 'url' => $mergeurl->out(false), 'label' => get_string('merge', 'capquiz'), ], 'delete' => [ 'type' => 'danger', 'method' => 'post', - 'url' => capquiz_urls::delete_qlist($srcqlist->id)->out(false), + 'url' => $deleteurl->out(false), 'label' => get_string('delete'), ], ]; } - return $this->renderer->render_from_template('capquiz/merge_with_question_list', ['lists' => $qlists]); + return ['lists' => $qlists]; } - } diff --git a/classes/output/instructor_dashboard.php b/classes/output/instructor_dashboard.php new file mode 100644 index 0000000..395d20b --- /dev/null +++ b/classes/output/instructor_dashboard.php @@ -0,0 +1,96 @@ +. + +namespace mod_capquiz\output; + +use mod_capquiz\capquiz; +use mod_capquiz\capquiz_question; +use mod_capquiz\capquiz_question_list; +use moodle_url; +use renderer_base; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/question/editlib.php'); + +/** + * Instructor dashboard. + * + * @package mod_capquiz + * @author Aleksander Skrede + * @copyright 2018 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class instructor_dashboard implements \renderable, \templatable { + + /** @var capquiz $capquiz */ + private capquiz $capquiz; + + /** + * Constructor. + * + * @param capquiz $capquiz + */ + public function __construct(capquiz $capquiz) { + $this->capquiz = $capquiz; + } + + /** + * Render instructor dashboard + */ + public function render(renderer_base $renderer): string { + return $renderer->render_from_template('capquiz/instructor_dashboard', $this->export_for_template($renderer)); + } + + /** + * Export parameters for template. + * + * @param renderer_base $output + * @return array + */ + public function export_for_template(renderer_base $output): array { + global $DB, $PAGE; + $qlist = capquiz_question_list::get_record(['capquiz_id' => $this->capquiz->get('id')]); + $publishmessage = null; + if ($this->capquiz->get('published')) { + $publishmessage = get_string('publish_already_published', 'capquiz'); + } else if (!$qlist || capquiz_question::count_records(['question_list_id' => $qlist->get('id')]) === 0) { + $publishmessage = get_string('publish_no_questions_in_list', 'capquiz'); + } + $publishquestionlisturl = new moodle_url('/mod/capquiz/action.php', [ + 'id' => $PAGE->cm->id, + 'action' => 'publish-question-list', + 'capquizid' => $this->capquiz->get('id'), + ]); + $usercount = $DB->count_records('capquiz_user', ['capquiz_id' => $this->capquiz->get('id')]); + $questioncount = $qlist ? capquiz_question::count_records(['question_list_id' => $qlist->get('id')]) : 0; + return [ + 'published_status' => get_string($this->capquiz->get('published') ? 'published' : 'not_published', 'capquiz'), + 'question_count' => $questioncount, + 'enrolled_student_count' => $usercount, + 'publishing' => [ + 'publishbutton' => [ + 'type' => 'primary', + 'method' => 'post', + 'url' => $publishquestionlisturl->out(false), + 'label' => get_string('publish', 'capquiz'), + 'disabled' => $this->capquiz->get('published') || $questioncount === 0, + ], + 'message' => $publishmessage, + ], + ]; + } +} diff --git a/classes/output/instructor_dashboard_renderer.php b/classes/output/instructor_dashboard_renderer.php deleted file mode 100755 index c459b7f..0000000 --- a/classes/output/instructor_dashboard_renderer.php +++ /dev/null @@ -1,157 +0,0 @@ -. - -/** - * This file defines a class used to render the instructor dashboard - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use mod_capquiz\capquiz_user; -use renderer_base; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/question/editlib.php'); - -/** - * Class instructor_dashboard_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class instructor_dashboard_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer_base $renderer */ - private renderer_base $renderer; - - /** - * Constructor. - * - * @param capquiz $capquiz - * @param renderer_base $renderer - */ - public function __construct(capquiz $capquiz, renderer_base $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - } - - /** - * Render instructor dashboard - */ - public function render(): string { - $html = $this->render_summary(); - $html .= $this->render_publish(); - $html .= $this->render_template(); - return $html; - } - - /** - * Render the instructor dashboard summary - */ - private function render_summary(): bool|string { - $qlist = $this->capquiz->question_list(); - if (!$qlist) { - return 'question list error'; - } - return $this->renderer->render_from_template('capquiz/instructor_dashboard_summary', [ - 'published_status' => get_string($this->capquiz->is_published() ? 'published' : 'not_published', 'capquiz'), - 'question_list_title' => $qlist->title(), - 'question_count' => $qlist->question_count(), - 'enrolled_student_count' => capquiz_user::user_count($this->capquiz->id()), - ]); - } - - /** - * Renders publish button - */ - private function render_publish(): bool|string { - $published = $this->capquiz->is_published(); - $canpublish = $this->capquiz->can_publish(); - $qlist = $this->capquiz->question_list(); - if (!$qlist) { - return 'question list error'; - } - $message = null; - if (!$canpublish) { - if ($qlist->question_count() === 0) { - $message = get_string('publish_no_questions_in_list', 'capquiz'); - } else if ($published) { - $message = get_string('publish_already_published', 'capquiz'); - } - } - return $this->renderer->render_from_template('capquiz/instructor_dashboard_publish', [ - 'publish' => $this->publish_button(), - 'message' => $message ?: false, - ]); - } - - /** - * Renders template - */ - private function render_template(): bool|string { - $qlist = $this->capquiz->question_list(); - if (!$qlist) { - return 'question list error'; - } - $message = null; - if (!$qlist->has_questions()) { - $message = get_string('template_no_questions_in_list', 'capquiz'); - } - return $this->renderer->render_from_template('capquiz/instructor_dashboard_template', [ - 'create_template' => $this->create_template_button(), - 'message' => $message ?: false, - ]); - } - - /** - * Creates publish button - */ - private function publish_button(): array { - return [ - 'type' => 'primary', - 'method' => 'post', - 'url' => capquiz_urls::question_list_publish_url($this->capquiz->question_list())->out(false), - 'label' => get_string('publish', 'capquiz'), - 'disabled' => !$this->capquiz->can_publish(), - ]; - } - - /** - * Creates create template button - */ - private function create_template_button(): array { - return [ - 'type' => 'primary', - 'method' => 'post', - 'url' => capquiz_urls::question_list_create_template_url($this->capquiz->question_list())->out(false), - 'label' => get_string('create_template', 'capquiz'), - 'disabled' => !$this->capquiz->question_list()->has_questions(), - ]; - } -} diff --git a/classes/output/matchmaking_configuration_renderer.php b/classes/output/matchmaking_configuration_renderer.php deleted file mode 100644 index c720301..0000000 --- a/classes/output/matchmaking_configuration_renderer.php +++ /dev/null @@ -1,109 +0,0 @@ -. - -/** - * This file defines a class used to render the matchmaking configuration view - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_matchmaking_strategy_loader; -use mod_capquiz\capquiz_urls; -use moodle_page; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/question/editlib.php'); - -/** - * Class matchmaking_configuration_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class matchmaking_configuration_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer $renderer */ - private renderer $renderer; - - /** @var capquiz_matchmaking_strategy_loader $registry */ - private capquiz_matchmaking_strategy_loader $registry; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * matchmaking_configuration_renderer constructor. - * @param capquiz $capquiz - * @param renderer $renderer - */ - public function __construct(capquiz $capquiz, renderer $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - $this->registry = new capquiz_matchmaking_strategy_loader($this->capquiz); - $this->page = $capquiz->get_page(); - } - - /** - * Calls submethod that renders the matchmaking_configuration view - */ - public function render(): bool|string { - if ($this->registry->has_strategy()) { - return $this->render_configuration(); - } else { - return '

' . get_string('no_matchmaking_strategy_selected', 'capquiz') . '

'; - } - } - - /** - * Renders the matchmaking configuration view - */ - private function render_configuration(): bool|string { - $strategy = $this->registry->current_strategy_name(); - return $this->renderer->render_from_template('capquiz/matchmaking_configuration', [ - 'strategy' => capquiz_matchmaking_strategy_loader::localized_strategy_name($strategy), - 'form' => $this->render_form(), - ]); - } - - /** - * Returns the rendered matchmaking configuration form - */ - private function render_form(): string { - $url = $this->page->url; - if ($form = $this->registry->configuration_form($url)) { - $formdata = $form->get_data(); - if ($formdata) { - $this->registry->configure_current_strategy($formdata); - $url = capquiz_urls::view_rating_system_url(); - redirect($url); - } - return $form->render(); - } - return get_string('nothing_to_configure_for_strategy', 'capquiz'); - } -} diff --git a/classes/output/matchmaking_strategy_selection_renderer.php b/classes/output/matchmaking_strategy_selection_renderer.php deleted file mode 100644 index 76c24ab..0000000 --- a/classes/output/matchmaking_strategy_selection_renderer.php +++ /dev/null @@ -1,99 +0,0 @@ -. - -/** - * This file defines a class used to render the matchmaking strategy selection form - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_matchmaking_strategy_loader; -use mod_capquiz\capquiz_matchmaking_strategy_registry; -use mod_capquiz\capquiz_urls; -use mod_capquiz\form\view\matchmaking_strategy_selection_form; -use moodle_page; -use moodle_url; - -/** - * class matchmaking_strategy_selection_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class matchmaking_strategy_selection_renderer { - - /** @var moodle_url $url */ - private moodle_url $url; - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer $renderer */ - private renderer $renderer; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * matchmaking_strategy_selection_renderer constructor. - * - * @param capquiz $capquiz - * @param renderer $renderer - */ - public function __construct(capquiz $capquiz, renderer $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - $this->url = capquiz_urls::view_rating_system_url(); - $this->page = $capquiz->get_page(); - } - - /** - * Sets redirect url - * - * @param moodle_url $url - */ - public function set_redirect_url(moodle_url $url): void { - $this->url = $url; - } - - /** - * Renders the matchmaking strategy selection form - */ - public function render(): bool|string { - $url = $this->page->url; - $form = new matchmaking_strategy_selection_form($this->capquiz, $url); - $formdata = $form->get_data(); - if ($formdata) { - $loader = new capquiz_matchmaking_strategy_loader($this->capquiz); - $registry = new capquiz_matchmaking_strategy_registry($this->capquiz); - $strategy = $registry->selection_strategies()[$formdata->strategy]; - $loader->set_strategy($strategy); - redirect($this->url); - } - return $this->renderer->render_from_template('capquiz/matchmaking_selection_strategy', [ - 'form' => $form->render(), - ]); - } - -} diff --git a/classes/output/question_attempt_renderer.php b/classes/output/question_attempt_renderer.php index 68ea873..f2e82c6 100755 --- a/classes/output/question_attempt_renderer.php +++ b/classes/output/question_attempt_renderer.php @@ -14,25 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class used to render a question attempt - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace mod_capquiz\output; -use core\context\module; +use mod_capquiz\api; use mod_capquiz\capquiz; use mod_capquiz\capquiz_question_list; use mod_capquiz\capquiz_user; -use mod_capquiz\capquiz_urls; -use mod_capquiz\capquiz_question; use mod_capquiz\capquiz_question_attempt; -use moodle_page; +use moodle_url; use question_display_options; use renderer_base; @@ -41,7 +30,7 @@ * * @package mod_capquiz * @author Aleksander Skrede - * @copyright 2018 NTNU + * @copyright 2018 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class question_attempt_renderer { @@ -52,9 +41,6 @@ class question_attempt_renderer { /** @var renderer_base $renderer */ private renderer_base $renderer; - /** @var moodle_page $page */ - private moodle_page $page; - /** * Constructor. * @@ -62,24 +48,14 @@ class question_attempt_renderer { * @param renderer_base $renderer */ public function __construct(capquiz $capquiz, renderer_base $renderer) { + global $USER; $this->capquiz = $capquiz; $this->renderer = $renderer; - $this->render_question_head_html(); - $this->page = $capquiz->get_page(); - } - - /** - * Renders the question head - */ - private function render_question_head_html(): void { - $user = $this->capquiz->user(); - $qengine = $this->capquiz->question_engine($user); - if ($qengine === null) { - return; - } - $attempt = $qengine->attempt_for_user($user); + $user = api::get_user($this->capquiz, $USER->id); + $attempt = api::get_question_attempt_for_user($capquiz, $user); if ($attempt !== null) { - $user->question_usage()->render_question_head_html($attempt->question_slot()); + $quba = api::get_question_usage_for_user($user); + $quba->render_question_head_html($attempt->get('slot')); } } @@ -87,68 +63,48 @@ private function render_question_head_html(): void { * Renders the question attempt view */ public function render(): string { - if (!$this->capquiz->is_published()) { + global $PAGE, $USER; + if (!$this->capquiz->get('published')) { return get_string('nothing_here_yet', 'capquiz'); } - $this->page->requires->js_call_amd('mod_capquiz/attempt', 'initialize', []); - $user = $this->capquiz->user(); - $qengine = $this->capquiz->question_engine($user); - $attempt = $qengine->attempt_for_user($user); - if ($attempt) { - if ($attempt->is_answered()) { - return $this->render_review($attempt); - } - if ($attempt->is_pending()) { - return $this->render_attempt($attempt, self::attempt_display_options($this->capquiz->context())); - } + $user = api::get_user($this->capquiz, $USER->id); + $attempt = api::get_question_attempt_for_user($this->capquiz, $user); + if (!$attempt) { + return get_string('you_finished_capquiz', 'capquiz'); + } + if ($attempt->get('answered')) { + $html = $this->render_progress($user); + $html .= $this->render_question_attempt($attempt, self::review_display_options()); + $submitreviewurl = new moodle_url('/mod/capquiz/async.php', [ + 'id' => $PAGE->cm->id, + 'action' => 'reviewed', + 'attempt' => $attempt->get('id'), + ]); + $html .= $this->renderer->render_from_template('core/single_button', [ + 'type' => 'primary', + 'method' => 'post', + 'url' => $submitreviewurl->out(false), + 'label' => get_string('next', 'capquiz'), + 'id' => 'capquiz_review_next', + ]); + return $html; + } + if (!$attempt->get('reviewed')) { + $html = $this->render_progress($user); + $html .= $this->render_question_attempt($attempt, self::attempt_display_options()); + return $html; } return get_string('you_finished_capquiz', 'capquiz'); } - /** - * Render the attempt - * - * @param capquiz_question_attempt $attempt - * @param question_display_options $options - */ - private function render_attempt(capquiz_question_attempt $attempt, question_display_options $options): string { - $user = $this->capquiz->user(); - $html = $this->render_progress($user); - $html .= $this->render_question_attempt($attempt, $options); - return $html; - } - - /** - * Render the attempt review - * - * @param capquiz_question_attempt $attempt - */ - private function render_review(capquiz_question_attempt $attempt): string { - $html = $this->render_attempt($attempt, self::review_display_options($this->capquiz->context())); - $html .= $this->render_review_next_button($attempt); - return $html; - } - - /** - * Render the review next button - * - * @param capquiz_question_attempt $attempt - */ - public function render_review_next_button(capquiz_question_attempt $attempt): string { - $url = capquiz_urls::response_reviewed_url($attempt); - $label = get_string('next', 'capquiz'); - return basic_renderer::render_action_button($this->renderer, $url, $label, id: 'capquiz_review_next'); - } - /** * Render a users progress * * @param capquiz_user $user */ private function render_progress(capquiz_user $user): string { - $qlist = $this->capquiz->question_list(); - $percent = $qlist->next_level_percent($this->capquiz, $user->rating()); - list($stars, $blankstars, $nostars) = $this->user_star_progress($user, $qlist); + $percent = api::get_percent_to_next_star($this->capquiz, $user->get('rating')); + list($stars, $blankstars, $nostars) = $this->user_star_progress($user, $this->capquiz); $student = [ 'up' => $percent >= 0 ? ['percent' => $percent] : false, 'down' => $percent < 0 ? ['percent' => -$percent] : false, @@ -168,44 +124,25 @@ private function render_progress(capquiz_user $user): string { * @param question_display_options $options */ public function render_question_attempt(capquiz_question_attempt $attempt, question_display_options $options): string { - $user = $this->capquiz->user(); - $quba = $user->question_usage(); - $this->page->requires->js_module('core_question_engine'); + global $PAGE, $USER; + $user = api::get_user($this->capquiz, $USER->id); + $PAGE->requires->js_module('core_question_engine'); + $submitattempturl = new moodle_url('/mod/capquiz/async.php', [ + 'id' => $PAGE->cm->id, + 'action' => 'answered', + 'attempt' => $attempt->get('id'), + ]); + $quba = api::get_question_usage_for_user($user); return $this->renderer->render_from_template('capquiz/student_question_attempt', [ 'attempt' => [ - 'url' => capquiz_urls::response_submit_url($attempt)->out(false), - 'body' => $quba->render_question($attempt->question_slot(), $options, $attempt->question_id()), + 'url' => $submitattempturl->out(false), + 'body' => $quba->render_question($attempt->get('slot'), $options, $attempt->get('question_id')), 'slots' => '', ], 'gradingdone' => $this->capquiz->is_grading_completed(), - 'finalgrade' => $user->highest_stars_graded(), - 'gradingpass' => $user->highest_stars_graded() >= $this->capquiz->stars_to_pass(), - 'duedate' => userdate($this->capquiz->time_due(), get_string('strftimedatetime', 'langconfig')), - ]); - } - - /** - * Render question attempts metainfo - * - * @param capquiz_user $user - * @param capquiz_question_attempt $attempt - */ - public function render_metainfo(capquiz_user $user, capquiz_question_attempt $attempt): string { - $question = capquiz_question::load($attempt->question_id()); - if ($question == null) { - return 'Question was not found.'; - } - return $this->renderer->render_from_template('capquiz/student_question_metainfo', [ - 'metainfo' => [ - 'rating' => [ - 'student' => $user->rating(), - 'question' => $question->rating(), - ], - 'question' => [ - 'capquiz_id' => $question->id(), - 'moodle_id' => $question->question_id(), - ], - ], + 'finalgrade' => $user->get('stars_graded'), + 'gradingpass' => $user->get('stars_graded') >= $this->capquiz->get('stars_to_pass'), + 'duedate' => userdate($this->capquiz->get('timedue'), get_string('strftimedatetime', 'langconfig')), ]); } @@ -213,16 +150,16 @@ public function render_metainfo(capquiz_user $user, capquiz_question_attempt $at * Checks a users star progress * * @param capquiz_user $user - * @param capquiz_question_list $qlist + * @param capquiz $capquiz * @return array[] */ - private function user_star_progress(capquiz_user $user, capquiz_question_list $qlist): array { + private function user_star_progress(capquiz_user $user, capquiz $capquiz): array { $stars = []; $blankstars = []; $nostars = []; - for ($star = 1; $star <= $qlist->max_stars(); $star++) { - if ($user->highest_stars_achieved() >= $star) { - if ($user->rating() >= $qlist->star_rating($star)) { + for ($star = 1; $star <= $capquiz->get_max_stars(); $star++) { + if ($user->get('highest_level') >= $star) { + if ($user->get('rating') >= $capquiz->get_required_rating_for_star($star)) { $stars[] = true; } else { $blankstars[] = true; @@ -236,12 +173,11 @@ private function user_star_progress(capquiz_user $user, capquiz_question_list $q /** * Returns the display options for the attempt review - * - * @param module $context */ - public static function review_display_options(module $context): question_display_options { + public static function review_display_options(): question_display_options { + global $PAGE; $options = new question_display_options(); - $options->context = $context; + $options->context = $PAGE->cm->context; $options->readonly = true; $options->flags = question_display_options::VISIBLE; $options->marks = question_display_options::VISIBLE; @@ -253,12 +189,11 @@ public static function review_display_options(module $context): question_display /** * Returns the display options for the attempt - * - * @param module $context */ - public static function attempt_display_options(module $context): question_display_options { + public static function attempt_display_options(): question_display_options { + global $PAGE; $options = new question_display_options(); - $options->context = $context; + $options->context = $PAGE->cm->context; $options->flags = question_display_options::HIDDEN; $options->marks = question_display_options::HIDDEN; $options->rightanswer = question_display_options::HIDDEN; diff --git a/classes/output/question_bank_renderer.php b/classes/output/question_bank_renderer.php deleted file mode 100644 index 661b538..0000000 --- a/classes/output/question_bank_renderer.php +++ /dev/null @@ -1,114 +0,0 @@ -. - -/** - * This file defines a class used to render a question bank - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use mod_capquiz\bank\question_bank_view; -use moodle_page; - -/** - * Class question_bank_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class question_bank_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * question_bank_renderer constructor. - * @param capquiz $capquiz - */ - public function __construct(capquiz $capquiz) { - $this->capquiz = $capquiz; - $this->page = $capquiz->get_page(); - } - - /** - * Creates question bank view - */ - public function create_view(): question_bank_view { - list($url, $contexts, $cmid, $cm, $capquizrecord, $pagevars) = $this->setup_question_edit(); - return new question_bank_view($contexts, $url, $this->capquiz->course(), $this->capquiz->course_module(), $pagevars); - } - - /** - * Renders question bank - */ - public function render(): string { - // phpcs:disable - // $questionsperpage = optional_param('qperpage', 10, PARAM_INT); - // $questionpage = optional_param('qpage', 0, PARAM_INT); - // phpcs:enable - $questionview = $this->create_view(); - // phpcs:disable - // $html = "

" . get_string('available_questions', 'capquiz') . "

"; - // phpcs:enable - ob_start(); - $questionview->display(); - $qbank = ob_get_clean(); - return \html_writer::div(\html_writer::div($qbank, 'bd'), 'questionbankformforpopup'); - } - - /** - * This is mostly a copy from editlib.php's question_edit_setup() function. - * The original function expects the course module id parameter to be "cmid", but this module gets passed "id" - * Moodle coding standard does not allow us to override $_GET or $_POST before calling question_edit_setup() - */ - private function setup_question_edit(): array { - $params = []; - $params['cmid'] = capquiz_urls::require_course_module_id_param(); - $params['qpage'] = optional_param('qpage', null, PARAM_INT); - $params['cat'] = optional_param('cat', null, PARAM_SEQUENCE); - $params['category'] = optional_param('category', null, PARAM_SEQUENCE); - $params['qperpage'] = optional_param('qperpage', null, PARAM_INT); - for ($i = 1; $i <= \core_question\local\bank\view::MAX_SORTS; $i++) { - $param = 'qbs' . $i; - if ($sort = optional_param($param, '', PARAM_TEXT)) { - $params[$param] = $sort; - } else { - break; - } - } - $params['recurse'] = optional_param('recurse', null, PARAM_BOOL); - $params['showhidden'] = optional_param('showhidden', null, PARAM_BOOL); - $params['qbshowtext'] = optional_param('qbshowtext', null, PARAM_BOOL); - $params['cpage'] = optional_param('cpage', null, PARAM_INT); - $params['qtagids'] = optional_param_array('qtagids', null, PARAM_INT); - $this->page->set_pagelayout('admin'); - $edittab = 'editq'; - return question_build_edit_resources($edittab, capquiz_urls::$urledit, $params); - } - -} diff --git a/classes/output/question_list.php b/classes/output/question_list.php new file mode 100644 index 0000000..246b306 --- /dev/null +++ b/classes/output/question_list.php @@ -0,0 +1,122 @@ +. + +namespace mod_capquiz\output; + +use mod_capquiz\api; +use mod_capquiz\capquiz_question; +use mod_capquiz\capquiz_question_list; +use moodle_url; +use renderer_base; + +/** + * Class question_list_renderer + * + * @package mod_capquiz + * @author Aleksander Skrede + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_list implements \renderable, \templatable { + + /** @var capquiz_question_list $qlist */ + private capquiz_question_list $qlist; + + /** + * Constructor. + * + * @param capquiz_question_list $qlist + */ + public function __construct(capquiz_question_list $qlist) { + $this->qlist = $qlist; + } + + /** + * Render question list + */ + public function render(renderer_base $output): bool|string { + return $output->render_from_template('capquiz/question_list', $this->export_for_template($output)); + } + + /** + * Export parameters for template. + * + * @param renderer_base $output + * @return array + */ + public function export_for_template(renderer_base $output): array { + global $DB, $PAGE; + $PAGE->requires->js_call_amd('mod_capquiz/edit_questions', 'initialize', [$PAGE->cm->id]); + $rows = []; + $index = 1; + foreach (capquiz_question::get_records(['question_list_id' => $this->qlist->get('id')]) as $question) { + $edit = false; + $preview = false; + $remove = false; + if (api::get_course_for_question($question)) { + $targetblank = ['name' => 'target', 'value' => '_blank']; + $editurl = new moodle_url('/question/bank/editquestion/question.php', [ + 'cmid' => $PAGE->cm->id, + 'id' => $question->get('question_id'), + ]); + $edit = [ + 'url' => $editurl->out(false), + 'label' => get_string('edit'), + 'classes' => 'fa fa-edit', + 'attributes' => [$targetblank], + ]; + $previewurl = \qbank_previewquestion\helper::question_preview_url($question->get('question_id')); + $preview = [ + 'url' => $previewurl->out(false), + 'label' => get_string('preview'), + 'classes' => 'fa fa-search-plus', + 'attributes' => [$targetblank], + ]; + $removeurl = new moodle_url('/mod/capquiz/action.php', [ + 'id' => $PAGE->cm->id, + 'action' => 'remove-question', + 'question-id' => $question->get('id'), + ]); + $remove = [ + 'url' => $removeurl->out(false), + 'label' => get_string('remove', 'capquiz'), + 'classes' => 'fa fa-trash', + ]; + } + $corequestion = $DB->get_record('question', ['id' => $question->get('question_id')]); + $rows[] = [ + 'index' => $index, + 'name' => $corequestion ? $corequestion->name : get_string('missing_question', 'capquiz'), + 'rating' => round($question->get('rating'), 3), + 'question_id' => $question->get('id'), + 'delete' => $remove, + 'edit' => $edit, + 'preview' => $preview, + ]; + $index++; + } + if (capquiz_question::count_records(['question_list_id' => $this->qlist->get('id')]) > 0) { + $message = get_string('update_rating_explanation', 'capquiz'); + } else { + $message = get_string('question_list_no_questions', 'capquiz'); + } + return [ + 'questions' => $rows, + 'message' => $message, + ]; + } +} diff --git a/classes/output/question_list_creator_renderer.php b/classes/output/question_list_creator_renderer.php deleted file mode 100644 index be869e3..0000000 --- a/classes/output/question_list_creator_renderer.php +++ /dev/null @@ -1,97 +0,0 @@ -. - -/** - * This file defines a class used to render a capquiz' question list creator - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use mod_capquiz\capquiz_question_list; -use mod_capquiz\form\view\question_list_create_form; -use moodle_page; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/question/editlib.php'); - -/** - * Class question_list_creator_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class question_list_creator_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer $renderer */ - private renderer $renderer; - - /** @var moodle_page $PAGE */ - private moodle_page $page; - - /** - * Constructor. - * - * @param capquiz $capquiz The capquiz whose question list creator should be rendered - * @param renderer $renderer The renderer used to render the question list creator - */ - public function __construct(capquiz $capquiz, renderer $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - $this->page = $capquiz->get_page(); - } - - /** - * Renders the question list creator - */ - public function render(): bool|string { - $url = $this->page->url; - $form = new question_list_create_form($url); - $formdata = $form->get_data(); - if ($formdata) { - $ratings = [ - $formdata->level_1_rating, - $formdata->level_2_rating, - $formdata->level_3_rating, - $formdata->level_4_rating, - $formdata->level_5_rating, - ]; - $title = $formdata->title; - $description = $formdata->description; - $qlist = capquiz_question_list::create_new_instance($this->capquiz, $title, $description, $ratings); - if ($qlist) { - redirect(capquiz_urls::create_view_url(capquiz_urls::$urlview)); - } - capquiz_urls::redirect_to_front_page(); - } - return $this->renderer->render_from_template('capquiz/create_question_list', [ - 'form' => $form->render(), - ]); - } - -} diff --git a/classes/output/question_list_renderer.php b/classes/output/question_list_renderer.php deleted file mode 100644 index 0f360de..0000000 --- a/classes/output/question_list_renderer.php +++ /dev/null @@ -1,140 +0,0 @@ -. - -/** - * This file defines a class used to render question list - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use mod_capquiz\capquiz_question_list; -use moodle_page; -use moodle_url; - -/** - * Class question_list_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class question_list_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer $renderer */ - private renderer $renderer; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * Constructor. - * - * @param capquiz $capquiz The capquiz whose question list should be rendered - * @param renderer $renderer The renderer used to render the question list - */ - public function __construct(capquiz $capquiz, renderer $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - $this->page = $capquiz->get_page(); - } - - /** - * Renders question list - */ - public function render(): bool|string { - $cmid = $this->capquiz->course_module()->id; - $this->page->requires->js_call_amd('mod_capquiz/edit_questions', 'initialize', [$cmid]); - $qlist = $this->capquiz->question_list(); - if ($qlist?->has_questions()) { - return $this->render_questions($qlist); - } - $title = get_string('question_list', 'capquiz'); - $noquestions = get_string('question_list_no_questions', 'capquiz'); - return "

$title

$noquestions

"; - } - - /** - * Renders all the individual questions - * - * @param capquiz_question_list $qlist - */ - private function render_questions(capquiz_question_list $qlist): bool|string { - $rows = []; - $questions = $qlist->questions(); - for ($i = 0; $i < $qlist->question_count(); $i++) { - $question = $questions[$i]; - $courseid = $question->course_id(); - $editurl = new moodle_url('/question/bank/editquestion/question.php', [ - 'cmid' => $this->page->cm->id, - 'id' => $question->question_id(), - ]); - $previewurl = new moodle_url('/question/bank/previewquestion/preview.php', [ - 'cmid' => $this->page->cm->id, - 'id' => $question->question_id(), - ]); - $targetblank = ['name' => 'target', 'value' => '_blank']; - $edit = $courseid === 0 ? false : [ - 'url' => $editurl->out(false), - 'label' => get_string('edit'), - 'classes' => 'fa fa-edit', - 'attributes' => [$targetblank], - ]; - $preview = $courseid === 0 ? false : [ - 'url' => $previewurl->out(false), - 'label' => get_string('preview'), - 'classes' => 'fa fa-search-plus', - 'attributes' => [$targetblank], - ]; - $rows[] = [ - 'index' => $i + 1, - 'name' => $question->name(), - 'rating' => round($question->rating(), 3), - 'question_id' => $question->id(), - 'rating_url' => capquiz_urls::set_question_rating_url($question->id())->out(false), - 'delete' => [ - 'url' => capquiz_urls::remove_question_from_list_url($question->id())->out(false), - 'label' => get_string('remove', 'capquiz'), - 'classes' => 'fa fa-trash', - ], - 'edit' => $edit, - 'preview' => $preview, - ]; - } - $message = null; - if ($qlist->has_questions()) { - $message = get_string('update_rating_explanation', 'capquiz'); - } - return $this->renderer->render_from_template('capquiz/question_list', [ - 'default_rating' => $qlist->default_question_rating(), - 'questions' => $rows, - 'message' => $message ?: false, - ]); - } - -} diff --git a/classes/output/question_list_selection_renderer.php b/classes/output/question_list_selection_renderer.php deleted file mode 100755 index f882c82..0000000 --- a/classes/output/question_list_selection_renderer.php +++ /dev/null @@ -1,82 +0,0 @@ -. - -/** - * This file defines a class used to render a capquiz' question list selection - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use context_module; -use mod_capquiz\capquiz_question_list; -use mod_capquiz\capquiz_urls; -use renderer_base; - -/** - * Class question_list_selection_renderer - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class question_list_selection_renderer { - - /** @var renderer_base $renderer */ - private renderer_base $renderer; - - /** - * Constructor. - * - * @param renderer_base $renderer The renderer used to render the question list selection - */ - public function __construct(renderer_base $renderer) { - $this->renderer = $renderer; - } - - /** - * Renders the question list selection - */ - public function render(): bool|string { - $templates = capquiz_question_list::load_question_list_templates(); - $lists = []; - foreach ($templates as $template) { - $lists[] = [ - 'title' => $template->title(), - 'description' => $template->description(), - 'author' => $template->author()->username, - 'created' => date('Y-m-d H:i:s', substr($template->time_created(), 0, 10)), - 'url' => capquiz_urls::question_list_select_url($template), - ]; - } - - $createurl = capquiz_urls::view_create_question_list_url(); - $params = $createurl->params(); - $createurl->remove_all_params(); - $createlabel = get_string('create_question_list', 'capquiz'); - - return $this->renderer->render_from_template('capquiz/question_list_selection', [ - 'lists' => $lists, - 'create' => basic_renderer::render_action_button($this->renderer, $createurl, $createlabel, 'get', $params), - ]); - } - -} diff --git a/classes/output/question_list_selector.php b/classes/output/question_list_selector.php new file mode 100644 index 0000000..efc2f35 --- /dev/null +++ b/classes/output/question_list_selector.php @@ -0,0 +1,77 @@ +. + +namespace mod_capquiz\output; + +use mod_capquiz\capquiz_question_list; +use moodle_url; +use renderer_base; + +/** + * Question list selector. + * + * @package mod_capquiz + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_list_selector implements \renderable, \templatable { + + /** @var int $cmid */ + private int $cmid; + + /** + * Constructor. + * + * @param int $cmid + */ + public function __construct(int $cmid) { + $this->cmid = $cmid; + } + + /** + * Renders the question list selection + */ + public function render(renderer_base $output): bool|string { + return $output->render_from_template('capquiz/question_list_selector', $this->export_for_template($output)); + } + + /** + * Export parameters for template. + * + * @param renderer_base $output + * @return array + */ + public function export_for_template(renderer_base $output): array { + global $DB; + $lists = []; + foreach (capquiz_question_list::get_records(['is_template' => 1]) as $templateqlist) { + $authoruser = $DB->get_record('user', ['id' => $templateqlist->get('author')]); + $lists[] = [ + 'title' => $templateqlist->get('title'), + 'description' => $templateqlist->get('description'), + 'author' => $authoruser->username ?? '', + 'created' => date('Y-m-d H:i:s', substr($templateqlist->get('timecreated'), 0, 10)), + 'url' => new moodle_url('/mod/capquiz/action.php', [ + 'id' => $this->cmid, + 'action' => 'set-question-list', + 'question-list-id' => $templateqlist->get('id'), + ]), + ]; + } + return ['lists' => $lists]; + } +} diff --git a/classes/output/rating_system_configuration_renderer.php b/classes/output/rating_system_configuration_renderer.php deleted file mode 100644 index 5a4df1f..0000000 --- a/classes/output/rating_system_configuration_renderer.php +++ /dev/null @@ -1,107 +0,0 @@ -. - -/** - * This file defines a class used to render the rating system configuration view - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_rating_system_loader; -use mod_capquiz\capquiz_urls; -use moodle_page; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/question/editlib.php'); - -/** - * Class rating_system_configuration_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class rating_system_configuration_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer $renderer */ - private renderer $renderer; - - /** @var capquiz_rating_system_loader $registry */ - private capquiz_rating_system_loader $registry; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * rating_system_configuration_renderer constructor. - * @param capquiz $capquiz - * @param renderer $renderer - */ - public function __construct(capquiz $capquiz, renderer $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - $this->registry = new capquiz_rating_system_loader($capquiz); - $this->page = $capquiz->get_page(); - } - - /** - * Calls submethod that renders the rating_system_configuration view - */ - public function render(): bool|string { - if ($this->registry->has_rating_system()) { - return $this->render_configuration(); - } else { - return '

No rating system has been specified

'; - } - } - - /** - * Renders the rating configuration view - */ - private function render_configuration(): bool|string { - return $this->renderer->render_from_template('capquiz/rating_system_configuration', [ - 'strategy' => $this->registry->current_rating_system_name(), - 'form' => $this->render_form(), - ]); - } - - /** - * Renders the rating configuration form - */ - private function render_form(): string { - $url = $this->page->url; - if ($form = $this->registry->configuration_form($url)) { - $formdata = $form->get_data(); - if ($formdata) { - $this->registry->configure_current_rating_system($formdata); - redirect(capquiz_urls::view_rating_system_url()); - } - return $form->render(); - } - return 'There is nothing to configure for this rating system'; - } -} diff --git a/classes/output/rating_system_selection_renderer.php b/classes/output/rating_system_selection_renderer.php deleted file mode 100644 index d53dd9f..0000000 --- a/classes/output/rating_system_selection_renderer.php +++ /dev/null @@ -1,98 +0,0 @@ -. - -/** - * This file defines a class used to render the rating system selection form - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_rating_system_loader; -use mod_capquiz\capquiz_rating_system_registry; -use mod_capquiz\capquiz_urls; -use mod_capquiz\form\view\rating_system_selection_form; -use moodle_page; -use moodle_url; - -/** - * Class rating_system_selection_renderer - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class rating_system_selection_renderer { - - /** @var moodle_url $url */ - private moodle_url $url; - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var renderer $renderer */ - private renderer $renderer; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * rating_system_selection_renderer constructor. - * - * @param capquiz $capquiz - * @param renderer $renderer - */ - public function __construct(capquiz $capquiz, renderer $renderer) { - $this->capquiz = $capquiz; - $this->renderer = $renderer; - $this->url = capquiz_urls::view_rating_system_url(); - $this->page = $capquiz->get_page(); - } - - /** - * Sets redirect url - * - * @param moodle_url $url - */ - public function set_redirect_url(moodle_url $url): void { - $this->url = $url; - } - - /** - * Renders the rating system selection form - */ - public function render(): bool|string { - $url = $this->page->url; - $form = new rating_system_selection_form($this->capquiz, $url); - $formdata = $form->get_data(); - if ($formdata) { - $registry = new capquiz_rating_system_registry(); - $loader = new capquiz_rating_system_loader($this->capquiz); - $loader->set_rating_system($registry->rating_systems()[$formdata->rating_system]); - redirect($this->url); - } - return $this->renderer->render_from_template('capquiz/rating_system_selection', [ - 'form' => $form->render(), - ]); - } - -} diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 16c09db..0e7d194 100755 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -14,234 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class used as a superclass to different renderers - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace mod_capquiz\output; -use core_renderer; -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use moodle_url; -use renderer_base; -use tabobject; +use plugin_renderer_base; defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/basic_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/classlist_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/question_list_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/question_bank_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/question_attempt_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/unauthorized_view_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/question_list_creator_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/instructor_dashboard_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/matchmaking_configuration_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/grading_configuration_renderer.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/output/matchmaking_strategy_selection_renderer.php'); - /** - * Main plugin renderer for capquiz + * CAPQuiz renderer. * * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class renderer extends \plugin_renderer_base { - - /** - * Returns a reference to the current renderer - */ - public function output_renderer(): core_renderer|renderer_base { - return $this->output; - } - - /** - * Creates a tab - * - * @param string $name Name of the tab - * @param string $title Title of the tab - * @param moodle_url $link Link - */ - private function tab(string $name, string $title, moodle_url $link): tabobject { - return new tabobject($name, $link, get_string($title, 'capquiz')); - } - - /** - * Creates all tabs - * - * @param string $activetab The currently active cab - */ - private function tabs(string $activetab): bool|string { - $tabs = [ - $this->tab('view_dashboard', 'dashboard', capquiz_urls::view_url()), - $this->tab('view_rating_system', 'rating_system', capquiz_urls::view_rating_system_url()), - $this->tab('view_questions', 'questions', capquiz_urls::view_question_list_url()), - $this->tab('view_grading', 'grading', capquiz_urls::view_grading_url()), - $this->tab('view_classlist', 'classlist', capquiz_urls::view_classlist_url()), - $this->tab('view_import', 'other_question_lists', capquiz_urls::view_import_url()), - $this->tab('view_report', 'reports', capquiz_urls::view_report_url()), - ]; - return print_tabs([$tabs], $activetab, null, null, true); - } - - /** - * Display a tabbed view - * - * @param string $view - * @param string $activetab - */ - public function display_tabbed_view(string $view, string $activetab): void { - echo $this->output->header(); - echo $this->tabs($activetab); - echo $view; - echo $this->output->footer(); - } - - /** - * Display multiple tabbed views - * - * @param string[] $views The renderers to render the tabs - * @param string $activetab The currently active tab - */ - public function display_tabbed_views(array $views, string $activetab): void { - echo $this->output->header(); - echo $this->tabs($activetab); - foreach ($views as $view) { - echo $view; - } - echo $this->output->footer(); - } - - /** - * Display view. - * - * @param string $view - */ - public function display_view(string $view): void { - echo $this->output->header(); - echo $view; - echo $this->output->footer(); - } - - /** - * Display the question attempt view - * - * @param capquiz $capquiz - */ - public function display_question_attempt_view(capquiz $capquiz): void { - $renderer = new question_attempt_renderer($capquiz, $this); - $this->display_view($renderer->render()); - } - - /** - * Display the instructor dashboard - * - * @param capquiz $capquiz - */ - public function display_instructor_dashboard(capquiz $capquiz): void { - $renderer = new instructor_dashboard_renderer($capquiz, $this); - $this->display_tabbed_view($renderer->render(), 'view_dashboard'); - } - - /** - * Display the question list create view - * - * @param capquiz $capquiz - */ - public function display_question_list_create_view(capquiz $capquiz): void { - $renderer = new question_list_creator_renderer($capquiz, $this); - $this->display_view($renderer->render()); - } - - /** - * Display the choose question list view - */ - public function display_choose_question_list_view(): void { - $renderer = new question_list_selection_renderer($this); - $this->display_view($renderer->render()); - } - - /** - * Display the unauthorized view - */ - public function display_unauthorized_view(): void { - $renderer = new unauthorized_view_renderer($this); - $this->display_view($renderer->render()); - } - - /** - * Display the question list view - * - * @param capquiz $capquiz - */ - public function display_question_list_view(capquiz $capquiz): void { - $r1 = new question_list_renderer($capquiz, $this); - $r2 = new question_bank_renderer($capquiz); - $html = '
' . $r1->render() . '
'; - $html .= '
' . $r2->render() . '
'; - $this->display_tabbed_view($html, 'view_questions'); - } - - /** - * Display the rating system configuration - * - * @param capquiz $capquiz - */ - public function display_rating_system_configuration(capquiz $capquiz): void { - $this->display_tabbed_views([ - (new matchmaking_strategy_selection_renderer($capquiz, $this))->render(), - (new matchmaking_configuration_renderer($capquiz, $this))->render(), - (new rating_system_selection_renderer($capquiz, $this))->render(), - (new rating_system_configuration_renderer($capquiz, $this))->render(), - ], 'view_rating_system'); - } - - - /** - * Display the leaderboard view - * - * @param capquiz $capquiz - */ - public function display_leaderboard(capquiz $capquiz): void { - $renderer = new classlist_renderer($capquiz, $this); - $this->display_tabbed_view($renderer->render(), 'view_classlist'); - } - - /** - * Display the import view - * - * @param capquiz $capquiz - */ - public function display_import(capquiz $capquiz): void { - $renderer = new import_renderer($capquiz, $this); - $this->display_tabbed_view($renderer->render(), 'view_import'); - } - - /** - * Display the grading configuration view - * - * @param capquiz $capquiz - */ - public function display_grading_configuration(capquiz $capquiz): void { - $renderer = new grading_configuration_renderer($capquiz, $this); - $this->display_tabbed_view($renderer->render(), 'view_grading'); - } - - /** - * Display the report view - * - * @param capquiz $capquiz - */ - public function display_report(capquiz $capquiz): void { - $renderer = new report_renderer($capquiz); - $this->display_tabbed_view($renderer->render(), 'view_report'); - } +class renderer extends plugin_renderer_base { } diff --git a/classes/output/report_renderer.php b/classes/output/report_renderer.php deleted file mode 100644 index f6936ed..0000000 --- a/classes/output/report_renderer.php +++ /dev/null @@ -1,111 +0,0 @@ -. - -/** - * This file defines a class used to render a report - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -use capquiz_exception; -use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use mod_capquiz\report\capquiz_report_factory; -use moodle_page; -use tabobject; - -defined('MOODLE_INTERNAL') || die(); - -require_once(__DIR__ . '/../../report/reportfactory.php'); - -/** - * Class report_renderer - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class report_renderer { - - /** @var capquiz $capquiz */ - private capquiz $capquiz; - - /** @var moodle_page $page */ - private moodle_page $page; - - /** - * Constructor. - * - * @param capquiz $capquiz - */ - public function __construct(capquiz $capquiz) { - $this->capquiz = $capquiz; - $this->page = $capquiz->get_page(); - } - - /** - * Renders report - */ - public function render(): string { - global $CFG; - $html = ''; - $download = optional_param('download', '', PARAM_RAW); - $mode = optional_param('mode', '', PARAM_ALPHA); - - $reportlist = capquiz_report_list($this->capquiz->context()); - if (empty($reportlist)) { - return get_string('noreports', 'capquiz'); - } - if ($mode === '') { - // Default to first accessible report and redirect. - capquiz_urls::redirect_to_url(capquiz_urls::view_report_url(reset($reportlist))); - } - if (!in_array($mode, $reportlist)) { - throw new capquiz_exception('erroraccessingreport', 'capquiz', - $CFG->wwwroot . '/mod/capquiz/view.php?id=' . $this->capquiz->course()->id); - } - $report = capquiz_report_factory::make($mode); - $this->setup_report(); - - $row = []; - foreach ($reportlist as $rep) { - $url = capquiz_urls::view_report_url($rep); - $row[] = new tabobject('capquiz_' . $rep, $url, get_string('pluginname', 'capquizreport_' . $rep)); - } - $tabs[] = $row; - - $html .= print_tabs($tabs, 'capquiz_' . $mode, null, null, true); - - ob_start(); - $report->display($this->capquiz, $this->capquiz->course_module(), $this->capquiz->course(), $download); - $html .= ob_get_clean(); - return $html; - } - - /** - * Sets pagelayout to "report" - */ - private function setup_report(): void { - $this->page->set_pagelayout('report'); - } -} - diff --git a/classes/output/unauthorized_view_renderer.php b/classes/output/unauthorized_view_renderer.php deleted file mode 100755 index 2ea302e..0000000 --- a/classes/output/unauthorized_view_renderer.php +++ /dev/null @@ -1,57 +0,0 @@ -. - -/** - * This file defines a class used to render the unauthorized view - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\output; - -/** - * Class unauthorized_view_renderer - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class unauthorized_view_renderer { - - /** @var renderer */ - private renderer $renderer; - - /** - * Constructor. - * - * @param renderer $renderer - */ - public function __construct(renderer $renderer) { - $this->renderer = $renderer; - } - - /** - * Renders the "unauthorized" view - */ - public function render(): bool|string { - return $this->renderer->render_from_template('capquiz/unauthorized', []); - } - -} diff --git a/classes/plugininfo/capquizreport.php b/classes/plugininfo/capquizreport.php index ea6ab49..309bc39 100644 --- a/classes/plugininfo/capquizreport.php +++ b/classes/plugininfo/capquizreport.php @@ -14,15 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Subplugin info class. - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace mod_capquiz\plugininfo; use admin_settingpage; diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 1e34a07..0f965a6 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -14,16 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Privacy Subsystem implementation for mod_capquiz. - * - * @package mod_capquiz - * @author André Storhaug - * @author Sebastian Søviknes Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - namespace mod_capquiz\privacy; use context; @@ -41,8 +31,8 @@ * Privacy Subsystem implementation for mod_capquiz. * * @author André Storhaug - * @author Sebastian Søviknes Gundersen - * @copyright 2019 NTNU + * @author Sebastian Gundersen + * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements diff --git a/classes/bank/question_bank_view.php b/classes/question/bank/question_bank_view.php old mode 100755 new mode 100644 similarity index 50% rename from classes/bank/question_bank_view.php rename to classes/question/bank/question_bank_view.php index 8fc8212..bc668f5 --- a/classes/bank/question_bank_view.php +++ b/classes/question/bank/question_bank_view.php @@ -14,33 +14,52 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines a class represeting a question bank view. - * - * It is based on similar implementations from the Core Quiz, - * but intended to run in a pane rather than a modal overlay, - * some differences are needed. It includes legacy code from - * different versions of moodle, and should have been refactored. - * - * @package mod_capquiz - * @author Hans Georg Schaathun - * @copyright 2018/2022 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); -namespace mod_capquiz\bank; +namespace mod_capquiz\question\bank; use context; /** - * Class question_bank_view + * Question bank view for CAPQuiz. * * @package mod_capquiz + * @author Sebastian Gundersen * @author Aleksander Skrede - * @copyright 2018 NTNU + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class question_bank_view extends \core_question\local\bank\view { + /** + * Constructor. + */ + public function __construct() { + global $PAGE; + $params = [ + 'cmid' => $PAGE->cm->id, + 'qpage' => optional_param('qpage', null, PARAM_INT), + 'cat' => optional_param('cat', null, PARAM_SEQUENCE), + 'category' => optional_param('category', null, PARAM_SEQUENCE), + 'qperpage' => optional_param('qperpage', null, PARAM_INT), + 'recurse' => optional_param('recurse', null, PARAM_BOOL), + 'showhidden' => optional_param('showhidden', null, PARAM_BOOL), + 'qbshowtext' => optional_param('qbshowtext', null, PARAM_BOOL), + 'cpage' => optional_param('cpage', null, PARAM_INT), + 'qtagids' => optional_param_array('qtagids', null, PARAM_INT), + 'page' => 'questions', + ]; + for ($i = 1; $i <= \core_question\local\bank\view::MAX_SORTS; $i++) { + $sort = optional_param("qbs$i", '', PARAM_TEXT); + if ($sort) { + $params["qbs$i"] = $sort; + } else { + break; + } + } + list($url, $contexts, $cmid, $cm, $capquiz, $pagevars) = + question_build_edit_resources('editq', $PAGE->url->out_as_local_url(false, []), $params); + parent::__construct($contexts, $url, $PAGE->course, $cm, $pagevars); + } /** * Specify the column heading diff --git a/classes/bank/question_name_column.php b/classes/question/bank/question_name_column.php similarity index 99% rename from classes/bank/question_name_column.php rename to classes/question/bank/question_name_column.php index ec92b48..eb3182a 100644 --- a/classes/bank/question_name_column.php +++ b/classes/question/bank/question_name_column.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace mod_capquiz\bank; +namespace mod_capquiz\question\bank; use stdClass; diff --git a/classes/bank/question_name_text_column.php b/classes/question/bank/question_name_text_column.php similarity index 93% rename from classes/bank/question_name_text_column.php rename to classes/question/bank/question_name_text_column.php index e2b1f01..675e864 100644 --- a/classes/bank/question_name_text_column.php +++ b/classes/question/bank/question_name_text_column.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace mod_capquiz\bank; +namespace mod_capquiz\question\bank; use core_tag_tag; use html_writer; @@ -28,7 +28,6 @@ * the `quiz_question_tostring` method copied from Core Quiz' locallib. * * @package mod_capquiz - * @category question * @copyright 2009 Tim Hunt * @author 2022 Hans Georg Schaathun * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -68,11 +67,9 @@ protected function quiz_question_tostring(stdClass $question, bool $showicon = f // Question idnumber. if ($showidnumber && $question->idnumber !== null && $question->idnumber !== '') { - $result .= ' '; - $result .= html_writer::span( + $result .= ' ' . html_writer::span( html_writer::span(get_string('idnumber', 'question'), 'accesshide') - . ' ' . s($question->idnumber), 'badge badge-primary' - ); + . ' ' . s($question->idnumber), 'badge badge-primary'); } // Question tags. @@ -89,19 +86,21 @@ protected function quiz_question_tostring(stdClass $question, bool $showicon = f // Question text. if ($showquestiontext) { - $questiontext = question_utils::to_plain_text($question->questiontext, - $question->questiontextformat, ['noclean' => true, 'para' => false]); + $questiontext = question_utils::to_plain_text($question->questiontext, $question->questiontextformat, [ + 'noclean' => true, + 'para' => false, + ]); $questiontext = shorten_text($questiontext, 50); if ($questiontext) { $result .= ' ' . html_writer::span(s($questiontext), 'questiontext'); } } - return $result; } /** * Output the contents of this column. + * * @param object $question the row from the $question table, augmented with extra information. * @param string $rowclasses CSS class names that should be applied to this row of output. */ @@ -119,8 +118,8 @@ protected function display_content($question, $rowclasses): void { } /** - * Use table alias 'q' for the question table, or one of the - * ones from get_extra_joins. Every field requested must specify a table prefix. + * Use table alias 'q' for the question table, or one of the ones from get_extra_joins. + * Every field requested must specify a table prefix. */ public function get_required_fields(): array { $fields = parent::get_required_fields(); @@ -134,8 +133,7 @@ public function get_required_fields(): array { * If this column needs extra data (e.g. tags) then load that here. * * The extra data should be added to the question object in the array. - * Probably a good idea to check that another column has not already - * loaded the data you want. + * Probably a good idea to check that another column has not already loaded the data you want. * * @param stdClass[] $questions the questions that will be displayed. */ diff --git a/classes/rating_system/capquiz_rating_system_registry.php b/classes/rating_system/capquiz_rating_system_registry.php deleted file mode 100755 index efdd352..0000000 --- a/classes/rating_system/capquiz_rating_system_registry.php +++ /dev/null @@ -1,138 +0,0 @@ -. - -/** - * This file defines a class used as a registry for the rating system - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use coding_exception; -use moodle_url; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/capquiz/classes/rating_system/elo_rating/elo_rating_system.php'); -require_once($CFG->dirroot . '/mod/capquiz/classes/rating_system/elo_rating/elo_rating_system_form.php'); - -/** - * Class capquiz_rating_system_registry - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_rating_system_registry { - - /** @var callable[][] $systems */ - private array $systems; - - /** - * Constructor. - */ - public function __construct() { - $this->register_rating_systems(); - } - - /** - * Returns rating system - * - * @param string $system - */ - public function rating_system(string $system): capquiz_rating_system { - $value = $this->systems[$system]; - if (!$value) { - $this->throw_rating_system_exception($system); - } - return array_values($value)[0](); - } - - /** - * Returns configuration form - * - * @param string $system - * @param stdClass $configuration - * @param moodle_url $url - */ - public function configuration_form(string $system, stdClass $configuration, moodle_url $url) { - $value = $this->systems[$system]; - if ($value) { - $configfunc = array_values($value)[1]; - return $configfunc($url, $configuration); - } - $this->throw_rating_system_exception($system); - } - - /** - * Checks if this instance has a rating system - * - * @param string $system - */ - public function has_rating_system(string $system): bool { - return isset($this->systems[$system]); - } - - /** - * Returns the default rating system - */ - public function default_rating_system(): string { - // Default rating system is added first. - // Modify caquiz_rating_system_registry::register_rating_systems() to change this. - $ratingsystems = $this->rating_systems(); - return reset($ratingsystems); - } - - /** - * Returns the names of all rating systems. - * - * @return string[] - */ - public function rating_systems(): array { - return array_keys($this->systems); - } - - /** - * Registers rating systems - */ - private function register_rating_systems(): void { - // The first listed will be selected by default when creating a new activity. - $this->systems = [ - 'Elo' => [ - fn() => new elo_rating_system(), - fn(moodle_url $url, stdClass $config) => new elo_rating_system_form($config, $url), - ], - ]; - } - - /** - * Creates and throws exception - * - * @param string $system - */ - private function throw_rating_system_exception(string $system) { - $msg = "The specified rating system '$system' does not exist."; - $msg .= " Options are {'" . implode("', '", $this->rating_systems()); - $msg .= "'}. This issue must be fixed by a programmer"; - throw new coding_exception($msg); - } -} diff --git a/classes/rating_system/elo_rating/elo_rating_system.php b/classes/rating_system/elo_rating/elo_rating_system.php deleted file mode 100755 index b888d38..0000000 --- a/classes/rating_system/elo_rating/elo_rating_system.php +++ /dev/null @@ -1,121 +0,0 @@ -. - -/** - * This file defines a class used as a registry for the rating system - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use stdClass; - -/** - * Class elo_rating_system - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class elo_rating_system extends capquiz_rating_system { - - /** @var float $studentkfactor */ - private float $studentkfactor; - - /** @var float $questionkfactor */ - private float $questionkfactor; - - /** - * Configures the rating system - * - * @param stdClass $config - */ - public function configure(stdClass $config): void { - if ($config->student_k_factor) { - $this->studentkfactor = $config->student_k_factor; - } - if ($config->question_k_factor) { - $this->questionkfactor = $config->question_k_factor; - } - } - - /** - * Returns the current configuration - */ - public function configuration(): stdClass { - $config = new stdClass; - $config->student_k_factor = $this->studentkfactor; - $config->question_k_factor = $this->questionkfactor; - return $config; - } - - /** - * Returns the default configuration - */ - public function default_configuration(): stdClass { - $config = new stdClass; - $config->student_k_factor = 32; - $config->question_k_factor = 8; - return $config; - } - - /** - * Updates the users rating - * - * @param capquiz_user $user - * @param capquiz_question $question - * @param float $score - */ - public function update_user_rating(capquiz_user $user, capquiz_question $question, float $score): void { - $current = $user->rating(); - $factor = $this->studentkfactor; - $newrating = $current + $factor * ($score - $this->expected_result($current, $question->rating())); - $user->set_rating($newrating); - } - - /** - * Updates the winning and losing questions ratings - * - * @param capquiz_question $winner - * @param capquiz_question $loser - */ - public function question_victory_ratings(capquiz_question $winner, capquiz_question $loser): void { - $loserating = $loser->rating(); - $winrating = $winner->rating(); - $factor = $this->questionkfactor; - $newloserating = $loserating + $factor * (0 - $this->expected_result($loserating, $winrating)); - $newwinrating = $winrating + $factor * (1 - $this->expected_result($winrating, $loserating)); - $loser->set_rating($newloserating); - $winner->set_rating($newwinrating); - } - - /** - * Calculates the expected score in favour of the player with rating $a, - * against a player with rating $b - * - * @param float $a - * @param float $b - */ - private function expected_result(float $a, float $b): float { - $exponent = ($b - $a) / 400.0; - return 1.0 / (1.0 + pow(10.0, $exponent)); - } -} diff --git a/classes/rating_system/elo_rating/elo_rating_system_form.php b/classes/rating_system/elo_rating/elo_rating_system_form.php deleted file mode 100755 index 05f35d2..0000000 --- a/classes/rating_system/elo_rating/elo_rating_system_form.php +++ /dev/null @@ -1,81 +0,0 @@ -. - -/** - * This file defines a class used to represent an elo rating system form - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -use moodle_url; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * Class elo_rating_system_form - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class elo_rating_system_form extends \moodleform { - - /** @var stdClass $config */ - private stdClass $config; - - /** - * Constructor. - * - * @param stdClass $config - * @param moodle_url $url - */ - public function __construct(stdClass $config, moodle_url $url) { - $this->config = $config; - parent::__construct($url); - } - - /** - * Defines rating system form - */ - public function definition(): void { - $form = $this->_form; - - $form->addElement('text', 'student_k_factor', get_string('student_k_factor', 'capquiz')); - $form->setType('student_k_factor', PARAM_INT); - $form->addRule('student_k_factor', get_string('student_k_factor_specified_rule', 'capquiz'), 'required', null, 'client'); - $form->addRule('student_k_factor', get_string('k_factor_numeric_rule', 'capquiz'), 'numeric', null, 'client'); - $form->setDefault('student_k_factor', $this->config->student_k_factor); - $form->addHelpButton('student_k_factor', 'student_k_factor', 'capquiz'); - - $form->addElement('text', 'question_k_factor', get_string('question_k_factor', 'capquiz')); - $form->setType('question_k_factor', PARAM_INT); - $form->addRule('question_k_factor', get_string('question_k_factor_specified_rule', 'capquiz'), 'required', null, 'client'); - $form->addRule('question_k_factor', get_string('k_factor_numeric_rule', 'capquiz'), 'numeric', null, 'client'); - $form->setDefault('question_k_factor', $this->config->question_k_factor); - $form->addHelpButton('question_k_factor', 'question_k_factor', 'capquiz'); - - $this->add_action_buttons(false); - } -} diff --git a/db/access.php b/db/access.php index 0c22e13..cff5c07 100755 --- a/db/access.php +++ b/db/access.php @@ -18,8 +18,8 @@ * File to define access policies * * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2018 NTNU + * @author Sebastian Gundersen + * @copyright 2018 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/db/install.xml b/db/install.xml index b5ca101..0971bcf 100755 --- a/db/install.xml +++ b/db/install.xml @@ -1,166 +1,154 @@ - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- - - - - - - - - - - -
- - - - - - - - - - - -
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+
diff --git a/db/upgrade.php b/db/upgrade.php index bb69056..7670bad 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -18,12 +18,11 @@ * File to keep track of upgrades to the capquiz plugin * * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2018 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -/** /** * Function to upgrade mod_capquiz * @@ -182,16 +181,11 @@ function xmldb_capquiz_upgrade($oldversion) { $dbman->add_key($atable, $aprevqprevrkey); } - $aurfield = new xmldb_field( - 'user_rating_id', XMLDB_TYPE_INTEGER, 11, null, null, null, null); - $aurkey = new xmldb_key( - 'user_rating_id', XMLDB_KEY_FOREIGN, ['user_rating_id'], 'capquiz_user_rating', ['id']); - $aprevurfield = new xmldb_field( - 'user_prev_rating_id', - XMLDB_TYPE_INTEGER, 11, null, null, null, null); - $aprevurkey = new xmldb_key( - 'user_prev_rating_id', - XMLDB_KEY_FOREIGN, ['user_prev_rating_id'], 'capquiz_user_rating', ['id']); + $aurfield = new xmldb_field('user_rating_id', XMLDB_TYPE_INTEGER, 11, null, null, null, null); + $aurkey = new xmldb_key('user_rating_id', XMLDB_KEY_FOREIGN, ['user_rating_id'], 'capquiz_user_rating', ['id']); + $aprevurfield = new xmldb_field('user_prev_rating_id', XMLDB_TYPE_INTEGER, 11, null, null, null, null); + $aprevurkey = new xmldb_key('user_prev_rating_id', XMLDB_KEY_FOREIGN, + ['user_prev_rating_id'], 'capquiz_user_rating', ['id']); if (!$dbman->field_exists($atable, $aurfield)) { $dbman->add_field($atable, $aurfield); @@ -254,7 +248,7 @@ function xmldb_capquiz_upgrade($oldversion) { $qlists = $DB->get_records('capquiz_question_list'); $totalqlists = count($qlists); $qlistindex = 0; - foreach ($qlists as &$qlist) { + foreach ($qlists as $qlist) { $qlistindex++; $oldqubaid = $qlist->question_usage_id; if (!$oldqubaid) { @@ -273,7 +267,7 @@ function xmldb_capquiz_upgrade($oldversion) { echo ''; echo ''; $userindex = 0; - foreach ($users as &$user) { + foreach ($users as $user) { // Create new question usage for user. $newquba = new stdClass(); @@ -297,7 +291,7 @@ function xmldb_capquiz_upgrade($oldversion) { , [$user->user_id, $oldqubaid]); $slot = 1; - foreach ($attempts as &$attempt) { + foreach ($attempts as $attempt) { $attempt->slot = $slot; $attempt->questionusageid = $newqubaid; $DB->update_record_raw('question_attempts', $attempt, true); @@ -346,7 +340,6 @@ function xmldb_capquiz_upgrade($oldversion) { upgrade_mod_savepoint(true, 2021020600, 'capquiz'); } if ($oldversion < 2021021100) { - $table = new xmldb_table('capquiz_attempt'); $key = new xmldb_key('user_id', XMLDB_KEY_FOREIGN, ['user_id'], 'users', ['id']); @@ -357,5 +350,164 @@ function xmldb_capquiz_upgrade($oldversion) { // Capquiz savepoint reached. upgrade_mod_savepoint(true, 2021021100, 'capquiz'); } + if ($oldversion < 2024101300) { + $table = new xmldb_table('capquiz'); + $field = new xmldb_field('questionselectiontype', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'nclosest'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + $field = new xmldb_field('numquestioncandidates', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '10'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + $field = new xmldb_field('minquestionsuntilreappearance', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + $field = new xmldb_field('userwinprobability', XMLDB_TYPE_NUMBER, '10, 2', null, XMLDB_NOTNULL, null, '0.75'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + $field = new xmldb_field('userkfactor', XMLDB_TYPE_NUMBER, '10, 2', null, XMLDB_NOTNULL, null, '32'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + $field = new xmldb_field('questionkfactor', XMLDB_TYPE_NUMBER, '10, 2', null, XMLDB_NOTNULL, null, '8'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + foreach ($DB->get_records('capquiz') as $capquiz) { + $selection = $DB->get_record('capquiz_question_selection', ['capquiz_id' => $capquiz->id]); + if ($selection) { + $config = json_decode($selection->configuration); + if ($selection->strategy === 'N-closest') { + $capquiz->numquestioncandidates = (int)$config->number_of_questions_to_select; + $capquiz->minquestionsuntilreappearance = (int)$config->prevent_same_question_for_turns; + $capquiz->userwinprobability = (float)$config->user_win_probability; + $capquiz->questionselectiontype = 'nclosest'; + } else { + $capquiz->questionselectiontype = 'chronological'; + } + $DB->update_record('capquiz', $capquiz); + $DB->delete_records('capquiz_question_selection', ['id' => $selection->id]); + } + $ratingsystem = $DB->get_record('capquiz_rating_system', ['capquiz_id' => $capquiz->id]); + if ($ratingsystem) { + $config = json_decode($ratingsystem->configuration); + $capquiz->userkfactor = (float)$config->student_k_factor; + $capquiz->questionkfactor = (float)$config->question_k_factor; + $DB->update_record('capquiz', $capquiz); + $DB->delete_records('capquiz_rating_system', ['id' => $ratingsystem->id]); + } + } + $table = new xmldb_table('capquiz_question_selection'); + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + $table = new xmldb_table('capquiz_rating_system'); + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + + upgrade_mod_savepoint(true, 2024101300, 'capquiz'); + } + + if ($oldversion < 2024102000) { + // Rename fields to standard names for capquiz_question_list. + $table = new xmldb_table('capquiz_question_list'); + $field = new xmldb_field('time_modified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $dbman->rename_field($table, $field, 'timemodified'); + $field = new xmldb_field('time_created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $dbman->rename_field($table, $field, 'timecreated'); + $field = new xmldb_field('default_question_rating', XMLDB_TYPE_FLOAT, '11', null, XMLDB_NOTNULL, null, '600'); + $dbman->rename_field($table, $field, 'defaultquestionrating'); + + // Fix default question rating type for capquiz_question_list. + $field = new xmldb_field('defaultquestionrating'); + $field->setType(XMLDB_TYPE_NUMBER); + $field->setLength(10); + $field->setDecimals('2'); + $dbman->change_field_type($table, $field); + + // Add timemodified and timecreated fields to capquiz_question. + $table = new xmldb_table('capquiz_question'); + $field = new xmldb_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + $field = new xmldb_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Add timemodified and timecreated fields to capquiz_user. + $table = new xmldb_table('capquiz_user'); + $field = new xmldb_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + $field = new xmldb_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Add timemodified and timecreated fields to capquiz_attempt. + $table = new xmldb_table('capquiz_attempt'); + $field = new xmldb_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + $field = new xmldb_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + upgrade_mod_savepoint(true, 2024102000, 'capquiz'); + } + + if ($oldversion < 2024102100) { + $table = new xmldb_table('capquiz'); + $field = new xmldb_field('defaultquestionrating', XMLDB_TYPE_NUMBER, '10, 2', null, XMLDB_NOTNULL, null, '600'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + foreach ($DB->get_records('capquiz') as $capquiz) { + $qlist = $DB->get_record('capquiz_question_list', ['capquiz_id' => $capquiz->id]); + if ($qlist) { + $capquiz->defaultquestionrating = $qlist->defaultquestionrating; + $DB->update_record('capquiz', $capquiz); + } + } + $table = new xmldb_table('capquiz_question_list'); + $field = new xmldb_field('defaultquestionrating'); + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + upgrade_mod_savepoint(true, 2024102100, 'capquiz'); + } + if ($oldversion < 2024102102) { + $table = new xmldb_table('capquiz'); + $field = new xmldb_field('stars_to_pass', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '3'); + $dbman->rename_field($table, $field, 'starstopass'); + $field = new xmldb_field('starratings', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, '1300,1450,1600,1800,2000'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + foreach ($DB->get_records('capquiz') as $capquiz) { + $qlist = $DB->get_record('capquiz_question_list', ['capquiz_id' => $capquiz->id]); + if ($qlist) { + $capquiz->starratings = $qlist->star_ratings; + $DB->update_record('capquiz', $capquiz); + } + } + $table = new xmldb_table('capquiz_question_list'); + $field = new xmldb_field('star_ratings'); + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + upgrade_mod_savepoint(true, 2024102102, 'capquiz'); + } return true; } diff --git a/edit.php b/edit.php deleted file mode 100755 index 7d34ce5..0000000 --- a/edit.php +++ /dev/null @@ -1,42 +0,0 @@ -. - -/** - * Edit capquiz - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -require_once("../../config.php"); -require_once($CFG->libdir . '/formslib.php'); -require_once($CFG->dirroot . '/mod/capquiz/lib.php'); - -$cmid = capquiz_urls::require_course_module_id_param(); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); -require_capability('mod/capquiz:instructor', $context); - -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urledit); -$bankrenderer = new output\question_bank_renderer($capquiz, $capquiz->renderer()); -$bankview = $bankrenderer->create_view(); -$capquiz->renderer()->display_question_list_view($capquiz); diff --git a/error.php b/error.php deleted file mode 100755 index 9ae7e08..0000000 --- a/error.php +++ /dev/null @@ -1,46 +0,0 @@ -. - -/** - * Error page - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -require_once("../../config.php"); -require_once($CFG->libdir . '/formslib.php'); -require_once($CFG->dirroot . '/mod/capquiz/lib.php'); - -$cmid = capquiz_urls::require_course_module_id_param(); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); - -$cmid = capquiz_urls::require_course_module_id_param(); -$capquiz = new capquiz($cmid); -if (!$capquiz) { - capquiz_urls::redirect_to_front_page(); -} - -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlerror); - -echo $capquiz->renderer()->output_renderer()->header(); -echo 'Something went wrong.'; -echo $capquiz->renderer()->output_renderer()->footer(); diff --git a/grade.php b/grade.php deleted file mode 100644 index a4e2e80..0000000 --- a/grade.php +++ /dev/null @@ -1,48 +0,0 @@ -. - -/** - * Displays the entry page into the capquiz - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -require_once('../../config.php'); - -require_login(); - -$cmid = required_param('id', PARAM_INT); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); - -$cmid = capquiz_urls::require_course_module_id_param(); -$capquiz = new capquiz($cmid); -if (!$capquiz) { - capquiz_urls::redirect_to_front_page(); -} - -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlview); - -if (has_capability('mod/capquiz:instructor', $capquiz->context())) { - redirect(capquiz_urls::view_classlist_url()); -} - -redirect(capquiz_urls::view_url()); diff --git a/index.php b/index.php index 69a1cf8..8539d34 100755 --- a/index.php +++ b/index.php @@ -19,18 +19,18 @@ * * @package mod_capquiz * @author Aleksander Skrede - * @copyright 2018 NTNU + * @copyright 2018 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace mod_capquiz; - require_once('../../config.php'); +global $DB, $PAGE; + require_login(); $courseid = required_param('id', PARAM_INT); $course = $DB->get_record('course', ['id' => $courseid]); if ($course) { - $PAGE->set_url(capquiz_urls::create_view_url(capquiz_urls::$urlview)); + $PAGE->set_url(new moodle_url('/mod/capquiz/view.php', ['id' => $PAGE->cm->id])); } diff --git a/lang/en/capquiz.php b/lang/en/capquiz.php index 587e20c..93bb7c2 100755 --- a/lang/en/capquiz.php +++ b/lang/en/capquiz.php @@ -19,7 +19,8 @@ * * @package mod_capquiz * @author Aleksander Skrede - * @copyright 2018 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -96,7 +97,6 @@ $string['lastname'] = 'Last name'; $string['level_rating'] = 'Rating required for {$a} stars'; -$string['level_rating_required'] = 'Rating required for {$a} stars is a required field'; $string['level_stars'] = '{$a} Stars'; $string['managecapquizreportplugins'] = 'Manage report plugins'; diff --git a/lib.php b/lib.php index a89e2f6..bc34e0d 100755 --- a/lib.php +++ b/lib.php @@ -20,15 +20,15 @@ * @package mod_capquiz * @author Aleksander Skrede * @author André Storhaug - * @copyright 2018 NTNU + * @copyright 2018 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -use mod_capquiz\capquiz; use mod_capquiz\output\question_attempt_renderer; defined('MOODLE_INTERNAL') || die(); +require_once($CFG->dirroot . '/mod/capquiz/adminlib.php'); require_once($CFG->dirroot . '/lib/gradelib.php'); /** @@ -36,11 +36,11 @@ * * @param stdClass $modformdata The data submitted from the form */ -function capquiz_add_instance(stdClass $modformdata): bool|int { +function capquiz_add_instance(stdClass $modformdata): int { global $DB; - $modformdata->time_modified = time(); - $modformdata->time_created = time(); - $modformdata->published = false; + $modformdata->timemodified = time(); + $modformdata->timecreated = time(); + $modformdata->published = 0; return $DB->insert_record('capquiz', $modformdata); } @@ -63,12 +63,7 @@ function capquiz_update_instance(stdClass $capquiz): bool { * @param int $cmid Course module id for the instance to be deleted */ function capquiz_delete_instance(int $cmid): void { - $capquiz = new capquiz($cmid); - $user = $capquiz->user(); - if ($user !== null) { - $quba = $user->question_usage(); - question_engine::delete_questions_usage_by_activity($quba->get_id()); - } + } /** @@ -112,6 +107,27 @@ function capquiz_cron(): bool { return true; } +/** + * Generates and returns list of available CAPQuiz report sub-plugins + * + * @return array list of valid reports present + */ +function capquiz_report_list(): array { + static $reportlist; + if (!empty($reportlist)) { + return $reportlist; + } + $reportlist = []; + $pluginmanager = new capquiz_plugin_manager('capquizreport'); + $enabledplugins = core_plugin_manager::instance()->get_enabled_plugins('capquizreport'); + foreach ($pluginmanager->get_sorted_plugins_list() as $reportname) { + if (isset($enabledplugins[$reportname])) { + $reportlist[] = $reportname; + } + } + return $reportlist; +} + /** * This function extends the settings navigation block for the site. * @@ -121,11 +137,65 @@ function capquiz_cron(): bool { function capquiz_extend_settings_navigation(settings_navigation $settings, navigation_node $capquiznode): void { global $PAGE, $CFG; + $context = $settings->get_page()->cm->context; + if (!has_capability('mod/capquiz:instructor', $context)) { + return; + } + $cmid = $PAGE->cm->id; + + $capquiznode->add_node(navigation_node::create( + get_string('questions', 'capquiz'), + new moodle_url('/mod/capquiz/view.php', ['id' => $cmid, 'page' => 'questions']), + navigation_node::TYPE_SETTING, + null, + 'capquiz_viewquestionlist', + new pix_icon('i/report', '') + )); + + $reportsnode = $capquiznode->add_node(navigation_node::create( + get_string('reports', 'capquiz'), + new moodle_url('/mod/capquiz/report.php', ['id' => $cmid]), + navigation_node::TYPE_SETTING, + null, + 'capquiz_viewreports', + new pix_icon('i/report', '') + )); + $reportsnode->showchildreninsubmenu = true; + + foreach (capquiz_report_list() as $reporttype) { + $reportnode = $reportsnode->add_node(navigation_node::create( + get_string('pluginname', "capquizreport_$reporttype"), + new moodle_url('/mod/capquiz/report.php', ['id' => $cmid, 'reporttype' => $reporttype]), + navigation_node::TYPE_SETTING, + null, + "capquiz_viewreport_$reporttype", + new pix_icon('i/report', '') + )); + } + + $capquiznode->add_node(navigation_node::create( + get_string('classlist', 'capquiz'), + new moodle_url('/mod/capquiz/view.php', ['id' => $cmid, 'page' => 'classlist']), + navigation_node::TYPE_SETTING, + null, + 'capquiz_viewclasslist', + new pix_icon('i/report', '') + )); + // Require {@link https://github.com/moodle/moodle/blob/master/lib/questionlib.php} // Included here as we only ever want to include this file if we really need to. require_once($CFG->libdir . '/questionlib.php'); question_extend_settings_navigation($capquiznode, $PAGE->cm->context)->trim_if_empty(); + + $capquiznode->add_node(navigation_node::create( + get_string('other_question_lists', 'capquiz'), + new moodle_url('/mod/capquiz/view.php', ['id' => $cmid, 'page' => 'import']), + navigation_node::TYPE_SETTING, + null, + 'capquiz_viewotherquestionlists', + new pix_icon('i/report', '') + )); } /** @@ -251,7 +321,7 @@ function capquiz_question_pluginfile(stdClass $course, stdClass $context, string $cm = get_coursemodule_from_instance('capquiz', $user->capquiz_id, $course->id, false, MUST_EXIST); require_login($course, false, $cm); $quba = question_engine::load_questions_usage_by_activity($qubaid); - $displayoptions = question_attempt_renderer::attempt_display_options(context_module::instance($cm->id)); + $displayoptions = question_attempt_renderer::attempt_display_options(); if (!$quba->check_file_access($slot, $displayoptions, $component, $filearea, $args, $forcedownload)) { send_file_not_found(); } @@ -265,31 +335,20 @@ function capquiz_question_pluginfile(stdClass $course, stdClass $context, string send_stored_file($file, 0, 0, $forcedownload, $options); } -// Ugly hack to make 3.11 and 4.0 work seamlessly. -if (!defined('FEATURE_MOD_PURPOSE')) { - define('FEATURE_MOD_PURPOSE', 'mod_purpose'); -} -if (!defined('MOD_PURPOSE_ASSESSMENT')) { - define('MOD_PURPOSE_ASSESSMENT', 'assessment'); -} - /** - * Checks if $feature is supported + * Checks if $feature is supported. * * @param string $feature */ -function capquiz_supports(string $feature) { - switch ($feature) { - case FEATURE_MOD_INTRO: - case FEATURE_BACKUP_MOODLE2: - case FEATURE_SHOW_DESCRIPTION: - case FEATURE_USES_QUESTIONS: - case FEATURE_GRADE_HAS_GRADE: - return true; - case FEATURE_MOD_PURPOSE: - return MOD_PURPOSE_ASSESSMENT; - default: - return false; - } +function capquiz_supports(string $feature): bool|string { + return match ($feature) { + FEATURE_MOD_INTRO, + FEATURE_BACKUP_MOODLE2, + FEATURE_SHOW_DESCRIPTION, + FEATURE_USES_QUESTIONS, + FEATURE_GRADE_HAS_GRADE => true, + FEATURE_MOD_PURPOSE => MOD_PURPOSE_ASSESSMENT, + default => false, + }; } diff --git a/locallib.php b/locallib.php deleted file mode 100644 index 2d46b01..0000000 --- a/locallib.php +++ /dev/null @@ -1,34 +0,0 @@ -. - -/** - * Library of internal classes and functions for module CAPQuiz - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * Base class for all the types of exception we throw. - * - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_exception extends moodle_exception { -} diff --git a/mod_form.php b/mod_form.php index bbf9629..deb707c 100755 --- a/mod_form.php +++ b/mod_form.php @@ -14,47 +14,231 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This files defines a form used to add/modify the capquiz module - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); + +use mod_capquiz\capquiz; +use mod_capquiz\capquiz_question_list; defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/course/moodleform_mod.php'); /** - * Class mod_capquiz_mod_form + * CAPQuiz settings form. * + * @author Sebastian Gundersen * @author Aleksander Skrede - * @copyright 2018 NTNU + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class mod_capquiz_mod_form extends moodleform_mod { - /** - * Defines the corm + * Defines the form. */ public function definition(): void { $form = $this->_form; + $form->addElement('text', 'name', get_string('name'), ['size' => '64']); $form->setType('name', PARAM_TEXT); $form->addRule('name', null, 'required', null, 'client'); $form->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); - $form->addElement('text', 'default_user_rating', get_string('default_user_rating', 'capquiz')); - $form->setType('default_user_rating', PARAM_INT); - $form->addRule('default_user_rating', get_string('default_rating_specified_rule', 'capquiz'), 'required', null, 'client'); - $form->addRule('default_user_rating', get_string('default_rating_numeric_rule', 'capquiz'), 'numeric', null, 'client'); - $form->setDefault('default_user_rating', 1200); + $this->standard_intro_elements(); + + if (!empty($this->_cm)) { + $capquiz = new capquiz((int)$this->_cm->instance); + $form->addElement('text', 'qlisttitle', get_string('title', 'capquiz')); + $form->setType('qlisttitle', PARAM_TEXT); + $form->addElement('textarea', 'qlistdescription', get_string('description', 'capquiz')); + $form->setType('qlistdescription', PARAM_TEXT); + } else { + $capquiz = new capquiz(); + } + + $defaultratingnumericrule = get_string('default_rating_numeric_rule', 'capquiz'); + + // Grading + $form->addElement('header', 'configuregradingheader', get_string('grading', 'capquiz')); + + $form->addElement('text', 'defaultuserrating', get_string('default_user_rating', 'capquiz')); + $form->setType('defaultuserrating', PARAM_INT); + $form->setDefault('defaultuserrating', $capquiz->get('defaultuserrating')); + $form->addRule('defaultuserrating', get_string('default_user_rating_required', 'capquiz'), 'required', null, 'client'); + $form->addRule('defaultuserrating', $defaultratingnumericrule, 'numeric', null, 'client'); + + $form->addElement('text', 'defaultquestionrating', get_string('default_question_rating', 'capquiz')); + $form->setType('defaultquestionrating', PARAM_INT); + $form->setDefault('defaultquestionrating', $capquiz->get('defaultquestionrating')); + $form->addRule('defaultquestionrating', get_string('field_required', 'capquiz'), 'required', null, 'client'); + $form->addRule('defaultquestionrating', $defaultratingnumericrule, 'numeric', null, 'client'); + + for ($star = 1; $star <= $capquiz->get_max_stars(); $star++) { + $input = "star_rating_$star"; + $text = get_string('level_rating', 'capquiz', $star); + $elements = []; + $elements[] = $form->createElement('text', $input, $text); + if ($star > 1) { + $elements[] = $form->createElement('submit', "delstarbutton$star", get_string('delete_star', 'capquiz')); + } + $form->addGroup($elements, "star_group_$star", $text, [''], false); + $form->setType($input, PARAM_INT); + $form->setDefault($input, $capquiz->get_required_rating_for_star($star)); + } + $form->addElement('submit', 'addstarbutton', get_string('add_star', 'capquiz')); + + $form->addElement('text', 'starstopass', get_string('stars_to_pass', 'capquiz')); + $form->setType('starstopass', PARAM_INT); + $form->setDefault('starstopass', $capquiz->get('stars_to_pass')); + $form->addRule('starstopass', get_string('stars_to_pass_required', 'capquiz'), 'required', null, 'client'); + + $form->addElement('date_time_selector', 'timedue', get_string('due_time_grading', 'capquiz')); + $form->setType('timedue', PARAM_INT); + $oneweek = 60 * 60 * 24 * 7; + $form->setDefault('timedue', $capquiz->get('timedue') ?: time() + $oneweek); + + // Question selection + $form->addElement('header', 'configurematchmakingstrategyheader', get_string('matchmaking', 'capquiz')); + $qselectiontypeindex = match ($capquiz->get('questionselectiontype')) { + 'nclosest' => 1, + 'chronological' => 2, + default => -1, + }; + $qselectiontypes = [ + $form->createElement('radio', 'qselectiontype', '', get_string('n_closest', 'capquiz'), 1, ['N-closest']), + $form->createElement('radio', 'qselectiontype', '', get_string('chronological', 'capquiz'), 2, ['Chronological']), + ]; + $form->addGroup($qselectiontypes, 'qselectiontypegroup', '', '
', false); + if ($qselectiontypeindex >= 1) { + $form->setDefault('qselectiontype', $qselectiontypeindex); + } + // N-closest + $form->addElement('text', 'numquestioncandidates', get_string('number_of_questions_to_select', 'capquiz')); + $form->setType('numquestioncandidates', PARAM_INT); + $form->setDefault('numquestioncandidates', $capquiz->get('numquestioncandidates')); + $form->addRule('numquestioncandidates', get_string('number_of_questions_to_select_required', 'capquiz'), 'required', null, 'client'); + $form->addHelpButton('numquestioncandidates', 'number_of_questions_to_select', 'capquiz'); + $form->hideIf('numquestioncandidates', 'qselectiontype', 'ne', 1); + + $form->addElement('text', 'minquestionsuntilreappearance', get_string('prevent_question_n_times', 'capquiz')); + $form->setType('minquestionsuntilreappearance', PARAM_INT); + $form->setDefault('minquestionsuntilreappearance', $capquiz->get('minquestionsuntilreappearance')); + $form->addRule('minquestionsuntilreappearance', get_string('field_required', 'capquiz'), 'required', null, 'client'); + $form->addHelpButton('minquestionsuntilreappearance', 'prevent_question_n_times', 'capquiz'); + $form->hideIf('minquestionsuntilreappearance', 'qselectiontype', 'ne', 1); + + $form->addElement('text', 'userwinprobability', get_string('user_win_probability', 'capquiz')); + $form->setType('userwinprobability', PARAM_FLOAT); + $form->setDefault('userwinprobability', $capquiz->get('userwinprobability')); + $form->addRule('userwinprobability', get_string('user_win_probability_required', 'capquiz'), 'required', null, 'client'); + $form->addHelpButton('userwinprobability', 'user_win_probability', 'capquiz'); + $form->hideIf('userwinprobability', 'qselectiontype', 'ne', 1); + + // Rating system + $form->addElement('header', 'configureratingsystemheader', get_string('rating_system', 'capquiz')); + + $form->addElement('text', 'userkfactor', get_string('student_k_factor', 'capquiz')); + $form->setType('userkfactor', PARAM_FLOAT); + $form->addRule('userkfactor', get_string('student_k_factor_specified_rule', 'capquiz'), 'required', null, 'client'); + $form->addRule('userkfactor', get_string('k_factor_numeric_rule', 'capquiz'), 'numeric', null, 'client'); + $form->setDefault('userkfactor', $capquiz->get('userkfactor')); + $form->addHelpButton('userkfactor', 'student_k_factor', 'capquiz'); + + $form->addElement('text', 'questionkfactor', get_string('question_k_factor', 'capquiz')); + $form->setType('questionkfactor', PARAM_FLOAT); + $form->addRule('questionkfactor', get_string('question_k_factor_specified_rule', 'capquiz'), 'required', null, 'client'); + $form->addRule('questionkfactor', get_string('k_factor_numeric_rule', 'capquiz'), 'numeric', null, 'client'); + $form->setDefault('questionkfactor', $capquiz->get('questionkfactor')); + $form->addHelpButton('questionkfactor', 'question_k_factor', 'capquiz'); - $this->standard_intro_elements(get_string('description')); $this->standard_coursemodule_elements(); $this->add_action_buttons(); } + /** + * Validate the data from the form. + * + * @param array $data + * @param array $files + * @return array + */ + public function validation($data, $files): array { + $errors = parent::validation($data, $files); + if (empty($data['defaultuserrating'])) { + $errors['defaultuserrating'] = get_string('default_user_rating_required', 'capquiz'); + } + if (empty($data['defaultquestionrating'])) { + $errors['defaultquestionrating'] = get_string('field_required', 'capquiz'); + } + if (empty($data['starstopass']) || $data['starstopass'] < 0 || $data['starstopass'] > 5) { + $errors['starstopass'] = get_string('stars_to_pass_required', 'capquiz'); + } + $nclosestvalidation = isset($data['qselectiontype']) && $data['qselectiontype'] === 1; + if ($nclosestvalidation) { + if (empty($data['userwinprobability'])) { + $errors['userwinprobability'] = get_string('user_win_probability_required', 'capquiz'); + } + if (empty($data['numquestioncandidates'])) { + $errors['numquestioncandidates'] = get_string('number_of_questions_to_select_required', 'capquiz'); + } + if (empty($data['minquestionsuntilreappearance'])) { + $errors['minquestionsuntilreappearance'] = get_string('field_required', 'capquiz'); + } + } + return $errors; + } + + /** + * Process data after default module form processing. + * + * @param stdClass $data + * @return void + */ + public function data_postprocessing($data): void { + parent::data_postprocessing($data); + if (empty($this->_cm->id)) { + return; + } + $capquiz = new capquiz((int)$this->_cm->instance); + $qlist = capquiz_question_list::get_record(['capquiz_id' => $capquiz->get('id')]); + if ($qlist) { + if ($data->qlisttitle) { + $qlist->set('title', $data->qlisttitle); + $qlist->save(); + } + if ($data->qlistdescription) { + $qlist->set('description', $data->qlistdescription); + $qlist->save(); + } + } + if ($data->defaultuserrating) { + $capquiz->set('defaultuserrating', $data->defaultuserrating); + } + if ($data->defaultquestionrating) { + $capquiz->set('defaultquestionrating', $data->defaultquestionrating); + } + if ($data->starstopass) { + $capquiz->set('stars_to_pass', $data->starstopass); + } + if ($data->timedue) { + $capquiz->set('timedue', $data->timedue); + } + $star = 1; + $ratings = []; + while (isset($data->{"star_rating_$star"})) { + if (!isset($data->{"delstarbutton$star"})) { + $ratings[] = (int)$data->{"star_rating_$star"}; + } + $star++; + } + if (isset($data->addstarbutton)) { + $ratings[] = end($ratings) + 100; + } + $starratings = implode(',', $ratings); + while (strlen($starratings) > 250) { + array_pop($ratings); + $starratings = implode(',', $ratings); + } + $capquiz->set('starratings', $starratings); + $capquiz->save(); + } } diff --git a/report.php b/report.php new file mode 100644 index 0000000..7f5c7a2 --- /dev/null +++ b/report.php @@ -0,0 +1,77 @@ +. + +/** + * Show a report. + * + * @package mod_capquiz + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use mod_capquiz\capquiz; +use mod_capquiz\local\reports\report; + +require_once(__DIR__ . '/../../config.php'); + +global $CFG, $OUTPUT, $PAGE; + +require_once($CFG->dirroot . '/mod/capquiz/report/reportlib.php'); + +$cmid = required_param('id', PARAM_INT); +$download = optional_param('download', '', PARAM_RAW); +$reporttype = optional_param('reporttype', '', PARAM_ALPHA); + +$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); +require_login($cm->course, false, $cm); + +$PAGE->set_context(context_module::instance($cmid)); +$PAGE->set_cm($cm); +$PAGE->set_pagelayout('report'); +$PAGE->set_url(new moodle_url('/mod/capquiz/report.php', ['id' => $cmid])); + +if (!has_capability('mod/capquiz:instructor', $PAGE->context)) { + throw new moodle_exception('erroraccessingreport', 'capquiz'); +} + +$availablereporttypes = capquiz_report_list(); + +if (empty($reporttype)) { + $reporttype = reset($availablereporttypes); + $PAGE->url->param('reporttype', $reporttype); +} + +if (!in_array($reporttype, $availablereporttypes)) { + throw new moodle_exception('erroraccessingreport', 'capquiz'); +} + +$filepath = "$CFG->dirroot/mod/capquiz/report/$reporttype/classes/report.php"; +if (!is_readable($filepath)) { + throw new moodle_exception("report type '$reporttype' doesn't provide expected file '$filepath'"); +} +include_once($filepath); +$classname = "capquizreport_$reporttype\\report"; +if (!class_exists($classname)) { + throw new moodle_exception("report type '$reporttype' doesn't define expected class '$classname' in '$filepath'"); +} + +/** @var report $report */ +$report = new $classname(); + +echo $OUTPUT->header(); +$report->display(new capquiz($cm->instance), $PAGE->cm, $PAGE->course, $download); +echo $OUTPUT->footer(); diff --git a/report/attempts/attempts_form.php b/report/attempts/classes/form.php similarity index 66% rename from report/attempts/attempts_form.php rename to report/attempts/classes/form.php index 6b86efc..831b2f2 100644 --- a/report/attempts/attempts_form.php +++ b/report/attempts/classes/form.php @@ -14,23 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * CAPQuiz attempts settings form definition. - * - * @package capquizreport_attempts - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace capquizreport_attempts; -use mod_capquiz\report\capquiz_attempts_report_form; -use MoodleQuickForm; - defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/mod/capquiz/report/attemptsreport_form.php'); +require_once($CFG->libdir . '/formslib.php'); /** * This is the settings form for the capquiz attempts report. @@ -39,7 +29,38 @@ * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquizreport_attempts_settings_form extends capquiz_attempts_report_form { +class form extends \moodleform { + /** + * Defines the form + */ + protected function definition(): void { + $mform = $this->_form; + + $mform->addElement('select', 'attempts', get_string('reportattemptsfrom', 'quiz'), [ + \mod_capquiz\local\reports\options::ENROLLED_WITH => get_string('reportuserswith', 'quiz'), + \mod_capquiz\local\reports\options::ENROLLED_WITHOUT => get_string('reportuserswithout', 'quiz'), + \mod_capquiz\local\reports\options::ENROLLED_ALL => get_string('reportuserswithorwithout', 'quiz'), + \mod_capquiz\local\reports\options::ALL_WITH => get_string('reportusersall', 'quiz'), + ]); + + $mform->addElement('advcheckbox', 'onlyanswered', '', get_string('reportshowonlyanswered', 'capquiz')); + $mform->addHelpButton('onlyanswered', 'reportshowonlyanswered', 'capquiz'); + + $mform->addElement('text', 'pagesize', get_string('pagesize', 'quiz')); + $mform->setType('pagesize', PARAM_INT); + + $mform->addGroup([ + $mform->createElement('advcheckbox', 'ansstate', '', get_string('ansstate', 'capquizreport_attempts')), + $mform->createElement('advcheckbox', 'urating', '', get_string('urating', 'capquizreport_attempts')), + $mform->createElement('advcheckbox', 'uprevrating', '', get_string('uprevrating', 'capquizreport_attempts')), + $mform->createElement('advcheckbox', 'qprevrating', '', get_string('qprevrating', 'capquizreport_attempts')), + $mform->createElement('advcheckbox', 'qtext', '', get_string('questiontext', 'quiz_responses')), + $mform->createElement('advcheckbox', 'resp', '', get_string('response', 'quiz_responses')), + $mform->createElement('advcheckbox', 'right', '', get_string('rightanswer', 'quiz_responses')), + ], 'coloptions', get_string('showthe', 'quiz_responses'), [' '], false); + + $mform->addElement('submit', 'submitbutton', get_string('showreport', 'quiz')); + } /** * Validate the data from the form. @@ -51,32 +72,10 @@ class capquizreport_attempts_settings_form extends capquiz_attempts_report_form */ public function validation($data, $files): array { $errors = parent::validation($data, $files); - if (!($data['urating'] - || $data['uprevrating'] - || $data['qprevrating'] - || $data['ansstate'] - || $data['qtext'] - || $data['resp'] - || $data['right'])) { + if (!($data['urating'] || $data['uprevrating'] || $data['qprevrating'] + || $data['ansstate'] || $data['qtext'] || $data['resp'] || $data['right'])) { $errors['coloptions'] = get_string('reportmustselectstate', 'quiz'); } return $errors; } - - /** - * Adds any additional preference fields to form - * - * @param MoodleQuickForm $mform the form to add preference fields to - */ - protected function other_preference_fields(MoodleQuickForm $mform): void { - $mform->addGroup([ - $mform->createElement('advcheckbox', 'ansstate', '', get_string('ansstate', 'capquizreport_attempts')), - $mform->createElement('advcheckbox', 'urating', '', get_string('urating', 'capquizreport_attempts')), - $mform->createElement('advcheckbox', 'uprevrating', '', get_string('uprevrating', 'capquizreport_attempts')), - $mform->createElement('advcheckbox', 'qprevrating', '', get_string('qprevrating', 'capquizreport_attempts')), - $mform->createElement('advcheckbox', 'qtext', '', get_string('questiontext', 'quiz_responses')), - $mform->createElement('advcheckbox', 'resp', '', get_string('response', 'quiz_responses')), - $mform->createElement('advcheckbox', 'right', '', get_string('rightanswer', 'quiz_responses')), - ], 'coloptions', get_string('showthe', 'quiz_responses'), [' '], false); - } } diff --git a/report/attempts/attempts_options.php b/report/attempts/classes/options.php similarity index 67% rename from report/attempts/attempts_options.php rename to report/attempts/classes/options.php index 175f967..79701c7 100644 --- a/report/attempts/attempts_options.php +++ b/report/attempts/classes/options.php @@ -14,55 +14,60 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Class to store the options for a {@see capquiz_attempts_report}. - * - * @package capquizreport_attempts - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace capquizreport_attempts; +use cm_info; use context_module; -use mod_capquiz\report\capquiz_attempts_report; -use mod_capquiz\report\capquiz_attempts_report_options; +use mod_capquiz\capquiz; use stdClass; defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/mod/capquiz/report/attemptsreport_options.php'); +require_once($CFG->dirroot . '/mod/capquiz/classes/local/reports/options.php'); /** - * Class to store the options for a {@see capquiz_attempts_report}. + * Options for the questions report table. * * @author André Storhaug * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquizreport_attempts_options extends capquiz_attempts_report_options { +class options extends \mod_capquiz\local\reports\options { /** @var bool whether to show the question answer state (correct or wrong) columns. */ - public $showansstate = true; + public bool $showansstate = true; /** @var bool whether to show the previous question rating columns. */ - public $showqprevrating = true; + public bool $showqprevrating = true; /** @var bool whether to show the user rating columns. */ - public $showurating = true; + public bool $showurating = true; /** @var bool whether to show the previous user rating columns. */ - public $showuprevrating = true; + public bool $showuprevrating = true; /** @var bool whether to show the question text columns. */ - public $showqtext = false; + public bool $showqtext = false; /** @var bool whether to show the students' response columns. */ - public $showresponses = false; + public bool $showresponses = false; /** @var bool whether to show the correct response columns. */ - public $showright = false; + public bool $showright = false; + + /** + * Constructor. + * + * @param capquiz $capquiz + * @param cm_info $cm + * @param stdClass $course + */ + public function __construct(capquiz $capquiz, cm_info $cm, stdClass $course) { + parent::__construct($capquiz, $cm, $course); + $this->reporttype = 'attempts'; + } /** * Get the current value of the settings to pass to the settings form. @@ -76,7 +81,6 @@ public function get_initial_form_data(): stdClass { $toform->qtext = $this->showqtext; $toform->resp = $this->showresponses; $toform->right = $this->showright; - return $toform; } @@ -86,14 +90,13 @@ public function get_initial_form_data(): stdClass { */ public function setup_from_form_data($fromform): void { parent::setup_from_form_data($fromform); - - $this->showansstate = $fromform->ansstate; - $this->showurating = $fromform->urating; - $this->showuprevrating = $fromform->uprevrating; - $this->showqprevrating = $fromform->qprevrating; - $this->showqtext = $fromform->qtext; - $this->showresponses = $fromform->resp; - $this->showright = $fromform->right; + $this->showansstate = (bool)$fromform->ansstate; + $this->showurating = (bool)$fromform->urating; + $this->showuprevrating = (bool)$fromform->uprevrating; + $this->showqprevrating = (bool)$fromform->qprevrating; + $this->showqtext = (bool)$fromform->qtext; + $this->showresponses = (bool)$fromform->resp; + $this->showright = (bool)$fromform->right; } /** @@ -101,7 +104,6 @@ public function setup_from_form_data($fromform): void { */ public function setup_from_params(): void { parent::setup_from_params(); - $this->showansstate = optional_param('ansstate', $this->showansstate, PARAM_BOOL); $this->showurating = optional_param('urating', $this->showurating, PARAM_BOOL); $this->showuprevrating = optional_param('uprevrating', $this->showuprevrating, PARAM_BOOL); @@ -117,14 +119,13 @@ public function setup_from_params(): void { */ public function setup_from_user_preferences(): void { parent::setup_from_user_preferences(); - - $this->showansstate = get_user_preferences('capquizreport_attempts_ansstate', $this->showansstate); - $this->showurating = get_user_preferences('capquizreport_attempts_urating', $this->showurating); - $this->showuprevrating = get_user_preferences('capquizreport_attempts_uprevrating', $this->showuprevrating); - $this->showqprevrating = get_user_preferences('capquizreport_attempts_qprevrating', $this->showqprevrating); - $this->showqtext = get_user_preferences('capquizreport_attempts_qtext', $this->showqtext); - $this->showresponses = get_user_preferences('capquizreport_attempts_resp', $this->showresponses); - $this->showright = get_user_preferences('capquizreport_attempts_right', $this->showright); + $this->showansstate = (bool)get_user_preferences('capquizreport_attempts_ansstate', $this->showansstate); + $this->showurating = (bool)get_user_preferences('capquizreport_attempts_urating', $this->showurating); + $this->showuprevrating = (bool)get_user_preferences('capquizreport_attempts_uprevrating', $this->showuprevrating); + $this->showqprevrating = (bool)get_user_preferences('capquizreport_attempts_qprevrating', $this->showqprevrating); + $this->showqtext = (bool)get_user_preferences('capquizreport_attempts_qtext', $this->showqtext); + $this->showresponses = (bool)get_user_preferences('capquizreport_attempts_resp', $this->showresponses); + $this->showright = (bool)get_user_preferences('capquizreport_attempts_right', $this->showright); } /** @@ -133,7 +134,6 @@ public function setup_from_user_preferences(): void { */ public function update_user_preferences(): void { parent::update_user_preferences(); - set_user_preference('capquizreport_attempts_ansstate', $this->showansstate); set_user_preference('capquizreport_attempts_urating', $this->showurating); set_user_preference('capquizreport_attempts_uprevrating', $this->showuprevrating); @@ -148,24 +148,17 @@ public function update_user_preferences(): void { */ public function resolve_dependencies(): void { parent::resolve_dependencies(); - - if (!$this->showansstate - && !$this->showurating - && !$this->showuprevrating - && !$this->showqprevrating - && !$this->showqtext - && !$this->showresponses - && !$this->showright) { + if (!$this->showansstate && !$this->showurating && !$this->showqprevrating && !$this->showuprevrating + && !$this->showqtext && !$this->showresponses && !$this->showright) { // We have to show at least something. $this->showansstate = true; $this->showurating = true; $this->showqprevrating = true; } - - // We only want to show the checkbox to delete attempts - // if the user has permissions and if the report mode is showing attempts. - $this->checkboxcolumn = has_capability('mod/capquiz:deleteattempts', context_module::instance($this->cm->id)) - && ($this->attempts != capquiz_attempts_report::ENROLLED_WITHOUT); + // We only want to show the checkbox to delete attempts if the user has permissions, + // and if the report type is showing attempts. + $candeleteattempts = has_capability('mod/capquiz:deleteattempts', context_module::instance($this->cm->id)); + $this->checkboxcolumn = $candeleteattempts && $this->attempts !== self::ENROLLED_WITHOUT; } /** diff --git a/report/attempts/classes/privacy/provider.php b/report/attempts/classes/privacy/provider.php index 74c3e70..149a781 100644 --- a/report/attempts/classes/privacy/provider.php +++ b/report/attempts/classes/privacy/provider.php @@ -14,14 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Privacy Subsystem implementation for capquizreport_attempts. - * - * @package capquizreport_attempts - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace capquizreport_attempts\privacy; @@ -36,10 +29,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - /** - * Get the language string identifier with the component's language - * file to explain why this plugin stores no data. + * Get the language string identifier with the component's language file to explain why this plugin stores no data. * * @return string */ diff --git a/report/attempts/classes/report.php b/report/attempts/classes/report.php new file mode 100644 index 0000000..f1d860c --- /dev/null +++ b/report/attempts/classes/report.php @@ -0,0 +1,205 @@ +. + +declare(strict_types=1); + +namespace capquizreport_attempts; + +use cm_info; +use context_course; +use context_module; +use mod_capquiz\capquiz; +use moodle_url; +use stdClass; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/capquiz/classes/local/reports/report.php'); +require_once($CFG->dirroot . '/mod/capquiz/report/attempts/classes/form.php'); +require_once($CFG->dirroot . '/mod/capquiz/report/attempts/classes/options.php'); +require_once($CFG->dirroot . '/mod/capquiz/report/attempts/classes/table.php'); + +/** + * The attempts report provides summary information about each attempt in a capquiz. + * + * @author André Storhaug + * @copyright 2019 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class report implements \mod_capquiz\local\reports\report { + /** + * Display attempts report. + * + * @param capquiz $capquiz + * @param cm_info $cm + * @param stdClass $course + * @param string $download + */ + public function display(capquiz $capquiz, cm_info $cm, stdClass $course, string $download): void { + global $DB, $CFG, $OUTPUT, $PAGE; + $context = context_module::instance($cm->id); + $studentsjoins = get_enrolled_with_capabilities_join($context); + $baseurl = new moodle_url('/mod/capquiz/report.php', ['id' => $context->instanceid, 'reporttype' => 'attempts']); + $form = new form($baseurl, ['capquiz' => $capquiz, 'context' => $context]); + $options = new options($capquiz, $cm, $course); + if ($fromform = $form->get_data()) { + $options->process_settings_from_form($fromform); + } else { + $options->process_settings_from_params(); + } + $form->set_data($options->get_initial_form_data()); + $questions = capquiz_report_get_questions($capquiz); + $courseshortname = format_string($course->shortname, true, ['context' => context_course::instance($course->id)]); + $table = new table($capquiz, $context, $options, $studentsjoins, $questions, $options->get_url()); + $filenamesuffix = get_string('attemptsfilename', 'capquizreport_attempts'); + $capquizname = format_string($capquiz->get('name')); + $filename = "$courseshortname - $capquizname - $filenamesuffix"; + $table->is_downloading($options->download, $filename, "$courseshortname $capquizname"); + if ($table->is_downloading()) { + raise_memory_limit(MEMORY_EXTRA); + } + $hasstudents = false; + if (!empty($studentsjoins->joins)) { + $sql = "SELECT DISTINCT u.id + FROM {user} u + $studentsjoins->joins + WHERE $studentsjoins->wheres"; + $hasstudents = $DB->record_exists_sql($sql, $studentsjoins->params); + } + $hasquestions = capquiz_has_questions($capquiz->get('id')); + if (!$table->is_downloading()) { + $PAGE->set_title($capquiz->get('name')); + $PAGE->set_heading($course->fullname); + $title = get_string('pluginname', 'capquizreport_attempts') . ' ' . get_string('report', 'capquiz'); + echo $OUTPUT->heading(format_string($title, true, ['context' => context_module::instance($cm->id)])); + echo \html_writer::div(get_string('attemptsnum', 'quiz', capquiz_report_num_attempt($capquiz->get('id')))); + if (!$hasquestions) { + echo capquiz_no_questions_message($cm, $context); + } else if (!$capquiz->get('published')) { + echo capquiz_not_published_message($cm, $context); + } else if (!$hasstudents) { + echo $OUTPUT->notification(get_string('nostudentsyet')); + } + $form->display(); + } + if (!$hasquestions || empty($questions)) { + return; + } + if (!$hasstudents && $options->attempts !== \mod_capquiz\local\reports\options::ALL_WITH) { + return; + } + $table->setup_sql_queries($studentsjoins); + $columns = []; + $headers = []; + if (!$table->is_downloading() && $options->checkboxcolumn) { + $columns[] = 'checkbox'; + $headers[] = null; + } + if (!$table->is_downloading() && $CFG->grade_report_showuserimage) { + $columns[] = 'picture'; + $headers[] = ''; + } + if (!$table->is_downloading()) { + $columns[] = 'fullname'; + $headers[] = get_string('name'); + } else { + $columns[] = 'lastname'; + $headers[] = get_string('lastname'); + $columns[] = 'firstname'; + $headers[] = get_string('firstname'); + } + // Some extra fields are always displayed when downloading because there's no space constraint, + // therefore do not include in extra-field list. + $fields = \core_user\fields::for_identity($context); + if ($table->is_downloading()) { + $fields = $fields->including('institution', 'department', 'email')->get_required_fields(); + } + foreach ($fields->get_required_fields() as $field) { + $columns[] = $field; + $headers[] = \core_user\fields::get_display_name($field); + } + if ($table->is_downloading()) { + $columns[] = 'institution'; + $headers[] = get_string('institution'); + $columns[] = 'department'; + $headers[] = get_string('department'); + $columns[] = 'email'; + $headers[] = get_string('email'); + $columns[] = 'userid'; + $headers[] = get_string('userid', 'capquiz'); + $columns[] = 'moodlequestionid'; + $headers[] = get_string('moodlequestionid', 'capquiz'); + } + if ($options->showansstate) { + $columns[] = 'answerstate'; + $headers[] = get_string('answerstate', 'capquizreport_attempts'); + } + if ($options->showurating) { + $columns[] = 'userrating'; + $headers[] = get_string('userrating', 'capquiz'); + } + if ($options->showuprevrating) { + $columns[] = 'userprevrating'; + $headers[] = get_string('userprevrating', 'capquizreport_attempts'); + } + if ($options->showqprevrating) { + $columns[] = 'questionprevrating'; + $headers[] = get_string('questionprevrating', 'capquizreport_attempts'); + } + if ($table->is_downloading()) { + $columns[] = 'questionprevratingmanual'; + $headers[] = get_string('questionprevratingmanual', 'capquizreport_attempts'); + } + if ($table->is_downloading()) { + $columns[] = 'timeanswered'; + $headers[] = get_string('timeanswered', 'capquiz'); + $columns[] = 'timereviewed'; + $headers[] = get_string('timereviewed', 'capquiz'); + } + if ($options->showqtext) { + $columns[] = 'question'; + $headers[] = get_string('question'); + } + if ($options->showresponses) { + $columns[] = 'response'; + $headers[] = get_string('response', 'capquizreport_attempts'); + } + if ($options->showright) { + $columns[] = 'right'; + $headers[] = get_string('rightanswer', 'question'); + } + $table->define_columns($columns); + $table->define_headers($headers); + $table->sortable(true, 'uniqueid'); + $table->define_baseurl($options->get_url()); + $table->column_suppress('picture'); + $table->column_suppress('fullname'); + foreach (\core_user\fields::for_identity($context)->get_required_fields() as $field) { + $table->column_suppress($field); + } + $table->column_class('picture', 'picture'); + $table->column_class('lastname', 'bold'); + $table->column_class('firstname', 'bold'); + $table->column_class('fullname', 'bold'); + $table->no_sorting('answerstate'); + $table->no_sorting('question'); + $table->no_sorting('response'); + $table->no_sorting('right'); + $table->set_attribute('id', 'responses'); + $table->collapsible(true); + $table->out($options->pagesize, true); + } +} diff --git a/report/attempts/attempts_table.php b/report/attempts/classes/table.php similarity index 72% rename from report/attempts/attempts_table.php rename to report/attempts/classes/table.php index 258e3bc..4eb87d0 100644 --- a/report/attempts/attempts_table.php +++ b/report/attempts/classes/table.php @@ -14,28 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines the capquiz attempts table for showing question attempts. - * - * @package capquizreport_attempts - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace capquizreport_attempts; use core\context; use core\dml\sql_join; -use mod_capquiz\report\capquiz_attempts_report_options; -use mod_capquiz\report\capquiz_attempts_report_table; +use mod_capquiz\capquiz; use moodle_url; use stdClass; defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/mod/capquiz/report/attemptsreport_table.php'); require_once($CFG->dirroot . '/mod/quiz/locallib.php'); +require_once($CFG->dirroot . '/mod/capquiz/classes/local/reports/table.php'); /** * This is a table subclass for displaying the capquiz attempts report. @@ -44,37 +36,33 @@ * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquizreport_attempts_table extends capquiz_attempts_report_table { - +class table extends \mod_capquiz\local\reports\table { /** - * Constructor - * @param object $capquiz + * Constructor. + * + * @param capquiz $capquiz * @param context $context - * @param capquiz_attempts_report_options $options + * @param options $options * @param sql_join $studentsjoins * @param array $questions * @param moodle_url $reporturl */ - public function __construct($capquiz, context $context, capquiz_attempts_report_options $options, - sql_join $studentsjoins, $questions, $reporturl) { - parent::__construct('mod-capquiz-report-attempts-report', $capquiz, $context, - $options, $studentsjoins, $questions, $reporturl); + public function __construct(capquiz $capquiz, context $context, options $options, sql_join $studentsjoins, + array $questions, moodle_url $reporturl) { + parent::__construct('attempts', $capquiz, $context, $options, $studentsjoins, $questions, $reporturl); } /** - * Take the data returned from the db_query and go through all the rows - * processing each col using either col_{columnname} method or other_cols - * method or if other_cols returns NULL then put the data straight into the - * table. + * Take the data returned from the db_query and go through all the rows processing each col using either col_{columnname} + * method or other_cols method or if other_cols returns NULL then put the data straight into the table. * * After calling this function, don't forget to call close_recordset. */ - public function build_table() { - if (!$this->rawdata) { - return; + public function build_table(): void { + if ($this->rawdata) { + $this->strtimeformat = str_replace(',', ' ', get_string('strftimedatetimeseconds', 'capquiz')); + parent::build_table(); } - $this->strtimeformat = str_replace(',', ' ', get_string('strftimedatetimeseconds', 'capquiz')); - parent::build_table(); } /** @@ -114,7 +102,7 @@ public function data_col(int $slot, string $field, stdClass $attempt): string { return '-'; } $value = $this->field_from_extra_data($attempt, $slot, $field); - $summary = $value !== null ? trim($value) : '-'; + $summary = empty($value) ? '-' : trim($value); if ($this->is_downloading() && $this->is_downloading() != 'html') { return $summary; } @@ -163,9 +151,9 @@ public function col_answerstate(stdClass $attempt): string { } $state = $this->slot_state($attempt, $attempt->slot); if ($this->is_downloading()) { - return $state->__toString(); + return (string)$state; } else { - return $this->make_review_link($state, $attempt, $attempt->slot); + return $this->make_review_link((string)$state, $attempt, $attempt->slot); } } @@ -232,33 +220,12 @@ public function col_questionprevratingmanual(stdClass $attempt): string { * Does this report require the detailed information for each question from the question_attempts_steps table? */ protected function requires_latest_steps_loaded(): bool { - if ($this->options->showansstate - || $this->options->showqtext - || $this->options->showresponses - || $this->options->showright) { - return true; - } else { - return false; - } - } - - /** - * Is this a column that depends on joining to the latest state information? - * If so, return the corresponding slot. If not, return false. - * - * @param string $column - * @return false|int|mixed - */ - protected function is_latest_step_column($column) { - if (preg_match('/^(?:question|response|right)/', $column, $matches)) { - return $matches[1]; - } - return false; + return $this->options->showansstate || $this->options->showqtext || $this->options->showresponses + || $this->options->showright; } /** - * A chance for subclasses to modify the SQL after the count query has been generated, - * and before the full query is constructed. + * A chance for subclasses to modify the SQL after the count query is generated, and before the full query is constructed. * * @param string $fields SELECT list. * @param string $from JOINs part of the SQL. @@ -266,7 +233,7 @@ protected function is_latest_step_column($column) { * @param array $params Query params. * @return array with 4 elements ($fields, $from, $where, $params) as from base_sql. */ - protected function update_sql_after_count($fields, $from, $where, $params): array { + protected function update_sql_after_count(string $fields, string $from, string $where, array $params): array { $fields .= ', cq.question_id AS moodlequestionid, cqr.rating AS questionrating, @@ -276,10 +243,10 @@ protected function update_sql_after_count($fields, $from, $where, $params): arra pcur.rating AS prevuserrating, pcur.rating AS manualprevurating'; - $from .= "\nLEFT JOIN {capquiz_question_rating} cqr ON cqr.id = ca.question_rating_id"; - $from .= "\nLEFT JOIN {capquiz_question_rating} pcqr ON pcqr.id = ca.question_prev_rating_id"; - $from .= "\nLEFT JOIN {capquiz_user_rating} cur ON cur.id = ca.user_rating_id"; - $from .= "\nLEFT JOIN {capquiz_user_rating} pcur ON pcur.id = ca.user_prev_rating_id"; + $from .= " LEFT JOIN {capquiz_question_rating} cqr ON cqr.id = ca.question_rating_id + LEFT JOIN {capquiz_question_rating} pcqr ON pcqr.id = ca.question_prev_rating_id + LEFT JOIN {capquiz_user_rating} cur ON cur.id = ca.user_rating_id + LEFT JOIN {capquiz_user_rating} pcur ON pcur.id = ca.user_prev_rating_id"; return [$fields, $from, $where, $params]; } diff --git a/report/attempts/lang/en/capquizreport_attempts.php b/report/attempts/lang/en/capquizreport_attempts.php index 7618787..7319bf7 100644 --- a/report/attempts/lang/en/capquizreport_attempts.php +++ b/report/attempts/lang/en/capquizreport_attempts.php @@ -27,27 +27,17 @@ $string['answerstate'] = 'Answer state'; $string['attemptsfilename'] = 'attempts'; -$string['correct'] = 'Correct'; - -$string['false'] = 'False'; - $string['pluginname'] = 'Attempts'; -$string['privacy:metadata'] = 'The capquizreport attempts plugin does not store any personal data.'; +$string['privacy:metadata'] = 'The CAPQuiz attempts report plugin does not store any personal data.'; $string['qprevrating'] = 'question\'s previous rating'; $string['qrating'] = 'question rating'; -$string['question'] = 'Question'; $string['questionprevrating'] = 'Question\'s previous rating'; $string['questionprevratingmanual'] = 'Previous rating manually updated'; $string['rating_manually_updated'] = 'Previous rating was manually updated'; $string['response'] = 'Response'; -$string['rightanswer'] = 'Right answer'; - -$string['true'] = 'True'; $string['uprevrating'] = 'user\'s previous rating'; $string['urating'] = 'user rating'; $string['userprevrating'] = 'User\'s previous rating'; - -$string['wrong'] = 'Wrong'; diff --git a/report/attempts/report.php b/report/attempts/report.php deleted file mode 100644 index 285f79e..0000000 --- a/report/attempts/report.php +++ /dev/null @@ -1,247 +0,0 @@ -. - -/** - * CAPQuiz attempts report class. - * - * @package capquizreport_attempts - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace capquizreport_attempts; - -use context_course; -use mod_capquiz\capquiz; -use mod_capquiz\report\capquiz_attempts_report; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/capquiz/report/attemptsreport.php'); -require_once(__DIR__ . '/attempts_form.php'); -require_once(__DIR__ . '/attempts_table.php'); -require_once(__DIR__ . '/attempts_options.php'); - -/** - * The capquiz attempts report provides summary information about each attempt in a capquiz. - * - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquizreport_attempts_report extends capquiz_attempts_report { - - /** - * Displays the full report - * @param capquiz $capquiz capquiz object - * @param stdClass $cm - course_module object - * @param stdClass $course - course object - * @param string $download - type of download being requested - */ - public function display($capquiz, $cm, $course, $download): bool { - global $DB; - - list($studentsjoins) = $this->init('attempts', 'capquizreport_attempts\capquizreport_attempts_settings_form', - $capquiz, $cm, $course); - - $this->options = new capquizreport_attempts_options('attempts', $capquiz, $cm, $course); - - if ($fromform = $this->form->get_data()) { - $this->options->process_settings_from_form($fromform); - } else { - $this->options->process_settings_from_params(); - } - - $this->form->set_data($this->options->get_initial_form_data()); - - // Load the required questions. - $questions = capquiz_report_get_questions($capquiz); - - // Prepare for downloading, if applicable. - $courseshortname = format_string($course->shortname, true, ['context' => context_course::instance($course->id)]); - - $table = new capquizreport_attempts_table($capquiz, $this->context, - $this->options, $studentsjoins, $questions, $this->options->get_url()); - - $filename = capquiz_report_download_filename(get_string('attemptsfilename', 'capquizreport_attempts'), - $courseshortname, $capquiz->name()); - - $table->is_downloading($this->options->download, $filename, $courseshortname . ' ' . format_string($capquiz->name())); - - if ($table->is_downloading()) { - raise_memory_limit(MEMORY_EXTRA); - } - - $hasstudents = false; - if (!empty($studentsjoins->joins)) { - $sql = "SELECT DISTINCT u.id - FROM {user} u - $studentsjoins->joins - WHERE $studentsjoins->wheres"; - $hasstudents = $DB->record_exists_sql($sql, $studentsjoins->params); - } - - // phpcs:disable - // TODO enable when support for attempt deletion is implemented {@see delete_selected_attempts}. - // $this->process_actions($capquiz, $cm, $studentsjoins, $this->options->get_url()); - // phpcs:enable - - $hasquestions = capquiz_has_questions($capquiz->id()); - // Start output. - if (!$table->is_downloading()) { - // Only print headers if not asked to download data. - $this->print_standard_header_and_messages($cm, $course, $capquiz, $this->options, $hasquestions, $hasstudents); - - // Print the display options. - $this->form->display(); - } - - if ($hasquestions && !empty($questions) && ($hasstudents || $this->options->attempts == self::ALL_WITH)) { - $table->setup_sql_queries($studentsjoins); - - // Define table columns. - $columns = []; - $headers = []; - - if (!$table->is_downloading() && $this->options->checkboxcolumn) { - $columns[] = 'checkbox'; - $headers[] = null; - } - - $this->add_user_columns($table, $columns, $headers); - - if ($table->is_downloading()) { - $this->add_userid_column($columns, $headers); - $this->add_moodlequestionid_column($columns, $headers); - } - - if ($this->options->showansstate) { - $columns[] = 'answerstate'; - $headers[] = get_string('answerstate', 'capquizreport_attempts'); - } - - $this->add_rating_columns($columns, $headers); - - if ($table->is_downloading()) { - $columns[] = 'questionprevratingmanual'; - $headers[] = get_string('questionprevratingmanual', 'capquizreport_attempts'); - } - - if ($table->is_downloading()) { - $this->add_time_columns($columns, $headers); - } - - if ($this->options->showqtext) { - $columns[] = 'question'; - $headers[] = get_string('question', 'capquizreport_attempts'); - } - if ($this->options->showresponses) { - $columns[] = 'response'; - $headers[] = get_string('response', 'capquizreport_attempts'); - } - if ($this->options->showright) { - $columns[] = 'right'; - $headers[] = get_string('rightanswer', 'capquizreport_attempts'); - } - - $table->define_columns($columns); - $table->define_headers($headers); - $table->sortable(true, 'uniqueid'); - - // Set up the table. - $table->define_baseurl($this->options->get_url()); - - $this->configure_user_columns($table); - - $table->no_sorting('answerstate'); - $table->no_sorting('question'); - $table->no_sorting('response'); - $table->no_sorting('right'); - - $table->set_attribute('id', 'responses'); - - $table->collapsible(true); - - $table->out($this->options->pagesize, true); - - } - return true; - } - - /** - * Adds rating columns to this report - * - * @param array $columns columns to be added - * @param array $headers column headers - */ - protected function add_rating_columns(array &$columns, array &$headers) { - if ($this->options->showurating) { - $this->add_user_rating_column($columns, $headers); - } - if ($this->options->showuprevrating) { - $this->add_user_previous_rating_column($columns, $headers); - } - if ($this->options->showqprevrating) { - $this->add_question_previous_rating_column($columns, $headers); - } - } - - /** - * Adds a user rating column to this report - * - * @param array $columns columns to be added - * @param array $headers column headers - */ - protected function add_user_rating_column(array &$columns, array &$headers) { - $columns[] = 'userrating'; - $headers[] = get_string('userrating', 'capquiz'); - } - - /** - * Adds a column with a users previous rating - * - * @param array $columns columns to be added - * @param array $headers column headers - */ - protected function add_user_previous_rating_column(array &$columns, array &$headers) { - $columns[] = 'userprevrating'; - $headers[] = get_string('userprevrating', 'capquizreport_attempts'); - } - - /** - * Adds column with question rating - * - * @param array $columns columns to be added - * @param array $headers column headers - */ - protected function add_question_rating_column(array &$columns, array &$headers) { - $columns[] = 'questionrating'; - $headers[] = get_string('questionrating', 'capquiz'); - } - - /** - * Adds column with previous question ratings - * - * @param array $columns columns to be added - * @param array $headers column headers - */ - protected function add_question_previous_rating_column(array &$columns, array &$headers) { - $columns[] = 'questionprevrating'; - $headers[] = get_string('questionprevrating', 'capquizreport_attempts'); - } -} diff --git a/report/attempts/version.php b/report/attempts/version.php index b8ecd7d..23de367 100644 --- a/report/attempts/version.php +++ b/report/attempts/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024100700; +$plugin->version = 2024102100; $plugin->requires = 2022041901; $plugin->component = 'capquizreport_attempts'; $plugin->maturity = MATURITY_STABLE; -$plugin->release = '1.1.0'; +$plugin->release = '1.2.0'; diff --git a/report/attemptsreport.php b/report/attemptsreport.php deleted file mode 100644 index 9fe5bef..0000000 --- a/report/attemptsreport.php +++ /dev/null @@ -1,286 +0,0 @@ -. - -/** - * The file defines a base class to be used to build a report like the overview or responses report, with one row per attempt. - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\report; - -use capquizreport_attempts\capquizreport_attempts_options; -use context_module; -use core\context\module; -use core\dml\sql_join; -use mod_capquiz\capquiz; -use mod_quiz\local\reports\attempts_report_options_form; -use moodle_url; -use stdClass; -use table_sql; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/tablelib.php'); -require_once($CFG->dirroot . '/mod/capquiz/report/report.php'); - -/** - * Base class for capquiz reports that are basically a table with one row for each attempt. - * - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -abstract class capquiz_attempts_report extends report { - /** @var int default page size for reports. */ - const DEFAULT_PAGE_SIZE = 30; - - /** @var string constant used for the options, means all users with attempts. */ - const ALL_WITH = 'all_with'; - /** @var string constant used for the options, means only enrolled users with attempts. */ - const ENROLLED_WITH = 'enrolled_with'; - /** @var string constant used for the options, means only enrolled users without attempts. */ - const ENROLLED_WITHOUT = 'enrolled_without'; - /** @var string constant used for the options, means all enrolled users. */ - const ENROLLED_ALL = 'enrolled_any'; - - /** @var string the mode this report is. */ - protected $mode; - - /** @var module the capquiz context. */ - protected module $context; - - /** @var attempts_report_options_form The settings form to use. */ - protected $form; - - /** @var ?capquizreport_attempts_options the options affecting this report. */ - protected $options = null; - - /** - * Initialise various aspects of this report. - * - * @param string $mode - * @param string $formclass - * @param object $capquiz - * @param object $cm - * @param object $course - * @return array with four elements: - * 0 => integer the current group id (0 for none). - * 1 => \core\dml\sql_join Contains joins, wheres, params for all the students in this course. - * 2 => \core\dml\sql_join Contains joins, wheres, params for all the students in the current group. - * 3 => \core\dml\sql_join Contains joins, wheres, params for all the students to show in the report. - * Will be the same as either element 1 or 2. - */ - protected function init(string $mode, string $formclass, $capquiz, $cm, $course) { - $this->mode = $mode; - $this->context = context_module::instance($cm->id); - $studentsjoins = get_enrolled_with_capabilities_join($this->context); - $this->form = new $formclass($this->get_base_url(), ['capquiz' => $capquiz, 'context' => $this->context]); - return [$studentsjoins]; - } - - - /** - * Get the base URL for this report. - */ - protected function get_base_url(): moodle_url { - return new moodle_url('/mod/capquiz/view_report.php', ['id' => $this->context->instanceid, 'mode' => $this->mode]); - } - - /** - * Outputs the things you commonly want at the top of a capquiz report. - * - * Calls through to {@see print_header_and_tabs()} and then - * outputs the standard group selector, number of attempts summary, - * and messages to cover common cases when the report can't be shown. - * - * @param stdClass $cm the course_module information. - * @param stdClass $course the course settings. - * @param capquiz $capquiz the capquiz settings. - * @param capquiz_attempts_report_options $options the current report settings. - * @param bool $hasquestions whether there are any questions in the capquiz. - * @param bool $hasstudents whether there are any relevant students. - */ - protected function print_standard_header_and_messages(stdClass $cm, stdClass $course, capquiz $capquiz, - capquiz_attempts_report_options $options, bool $hasquestions, - bool $hasstudents): void { - global $OUTPUT; - - $this->print_header_and_tabs($cm, $course, $capquiz, $this->mode); - - // Print information on the number of existing attempts. - $strattemptnum = capquiz_num_attempt_summary($capquiz, true); - if (!empty($strattemptnum)) { - echo '
' . $strattemptnum . '
'; - } - - if (!$hasquestions) { - echo capquiz_no_questions_message($capquiz, $cm, $this->context); - } else if (!$capquiz->is_published()) { - echo capquiz_not_published_message($capquiz, $cm, $this->context); - } else if (!$hasstudents) { - echo $OUTPUT->notification(get_string('nostudentsyet')); - } - } - - /** - * Add all the user-related columns to the $columns and $headers arrays. - * - * @param table_sql $table the table being constructed. - * @param array $columns the list of columns. Added to. - * @param array $headers the columns headings. Added to. - */ - protected function add_user_columns(table_sql $table, array &$columns, array &$headers): void { - global $CFG; - if (!$table->is_downloading() && $CFG->grade_report_showuserimage) { - $columns[] = 'picture'; - $headers[] = ''; - } - if (!$table->is_downloading()) { - $columns[] = 'fullname'; - $headers[] = get_string('name'); - } else { - $columns[] = 'lastname'; - $headers[] = get_string('lastname'); - $columns[] = 'firstname'; - $headers[] = get_string('firstname'); - } - - // When downloading, some extra fields are always displayed (because - // there's no space constraint) so do not include in extra-field list. - $fields = \core_user\fields::for_identity($this->context); - if ($table->is_downloading()) { - $fields = $fields->including('institution', 'department', 'email')->get_required_fields(); - } - $extrafields = $fields->get_required_fields(); - - foreach ($extrafields as $field) { - $columns[] = $field; - $headers[] = \core_user\fields::get_display_name($field); - } - - if ($table->is_downloading()) { - $columns[] = 'institution'; - $headers[] = get_string('institution'); - - $columns[] = 'department'; - $headers[] = get_string('department'); - - $columns[] = 'email'; - $headers[] = get_string('email'); - } - } - - /** - * Add the state column to the $columns and $headers arrays. - * - * @param array $columns the list of columns. Added to. - * @param array $headers the columns headings. Added to. - */ - protected function add_questionid_column(array &$columns, array &$headers): void { - $columns[] = 'questionid'; - $headers[] = get_string('questionid', 'capquiz'); - } - - /** - * Add the state column to the $columns and $headers arrays. - * - * @param array $columns the list of columns. Added to. - * @param array $headers the columns headings. Added to. - */ - protected function add_moodlequestionid_column(array &$columns, array &$headers): void { - $columns[] = 'moodlequestionid'; - $headers[] = get_string('moodlequestionid', 'capquiz'); - } - - /** - * Add the state column to the $columns and $headers arrays. - * - * @param array $columns the list of columns. Added to. - * @param array $headers the columns headings. Added to. - */ - protected function add_userid_column(array &$columns, array &$headers): void { - $columns[] = 'userid'; - $headers[] = get_string('userid', 'capquiz'); - } - - /** - * Add all the time-related columns to the $columns and $headers arrays. - * - * @param array $columns the list of columns. Added to. - * @param array $headers the columns headings. Added to. - */ - protected function add_time_columns(array &$columns, array &$headers): void { - $columns[] = 'timeanswered'; - $headers[] = get_string('timeanswered', 'capquiz'); - - $columns[] = 'timereviewed'; - $headers[] = get_string('timereviewed', 'capquiz'); - } - - /** - * Set the display options for the user-related columns in the table. - * - * @param table_sql $table the table being constructed. - */ - protected function configure_user_columns(table_sql $table): void { - $table->column_suppress('picture'); - $table->column_suppress('fullname'); - $extrafields = \core_user\fields::for_identity($this->context)->get_required_fields(); - foreach ($extrafields as $field) { - $table->column_suppress($field); - } - $table->column_class('picture', 'picture'); - $table->column_class('lastname', 'bold'); - $table->column_class('firstname', 'bold'); - $table->column_class('fullname', 'bold'); - } - - /** - * Process any submitted actions. - * - * @param stdClass $capquiz - * @param stdClass $cm the cm object for the capquiz. - * @param sql_join $allowedjoins (joins, wheres, params) the users whose attempt this user is allowed to modify. - * @param moodle_url $redirecturl where to redircet to after a successful action. - */ - protected function process_actions(stdClass $capquiz, stdClass $cm, sql_join $allowedjoins, moodle_url $redirecturl): void { - if (optional_param('delete', 0, PARAM_BOOL) && confirm_sesskey()) { - if ($attemptids = optional_param_array('attemptid', [], PARAM_INT)) { - require_capability('mod/capquiz:deleteattempts', $this->context); - $this->delete_selected_attempts($capquiz, $cm, $attemptids, $allowedjoins); - redirect($redirecturl); - } - } - } - - /** - * Delete the capquiz attempts. - * - * @param stdClass $capquiz the capquiz settings. Attempts that don't belong to this capquiz are not deleted. - * @param stdClass $cm the course_module object. - * @param array $attemptids the list of attempt ids to delete. - * @param sql_join $allowedjoins (joins, wheres, params) This list of userids that are visible in the report. - * Users can only delete attempts that they are allowed to see in the report. - * Empty means all users. - */ - protected function delete_selected_attempts(stdClass $capquiz, stdClass $cm, array $attemptids, sql_join $allowedjoins) { - // TODO implement to add support for attempt deletion. - } -} diff --git a/report/attemptsreport_form.php b/report/attemptsreport_form.php deleted file mode 100644 index 559dcf2..0000000 --- a/report/attemptsreport_form.php +++ /dev/null @@ -1,118 +0,0 @@ -. - -/** - * Base class for the settings form for {@see capquiz_attempts_report}s. - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\report; - -use moodleform; -use MoodleQuickForm; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir . '/formslib.php'); - -/** - * Base class for the settings form for {@see capquiz_attempts_report}s. - * - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -abstract class capquiz_attempts_report_form extends moodleform { - - /** - * Validate the data from the form. - * - * @param array $data array of ("fieldname"=>value) of submitted data - * @param array $files array of uploaded files "element_name"=>tmp_file_path - * @return array of "element_name"=>"error_description" if there are errors, - * or an empty array if everything is OK (true allowed for backwards compatibility too). - */ - public function validation($data, $files) { - $errors = parent::validation($data, $files); - return $errors; - } - - /** - * Defines the form - */ - protected function definition(): void { - $mform = $this->_form; - - $mform->addElement('header', 'preferencespage', get_string('reportwhattoinclude', 'quiz')); - $this->standard_attempt_fields($mform); - $this->other_attempt_fields($mform); - - $mform->addElement('header', 'preferencesuser', get_string('reportdisplayoptions', 'quiz')); - $this->standard_preference_fields($mform); - $this->other_preference_fields($mform); - - $mform->addElement('submit', 'submitbutton', get_string('showreport', 'quiz')); - } - - /** - * Adds the standard attempt fields to form - * - * @param MoodleQuickForm $mform the form to add attempt fields to - */ - protected function standard_attempt_fields(MoodleQuickForm $mform): void { - $mform->addElement('select', 'attempts', get_string('reportattemptsfrom', 'quiz'), [ - capquiz_attempts_report::ENROLLED_WITH => get_string('reportuserswith', 'quiz'), - // phpcs:disable - // capquiz_attempts_report::ENROLLED_WITHOUT => get_string('reportuserswithout', 'quiz'), - // capquiz_attempts_report::ENROLLED_ALL => get_string('reportuserswithorwithout', 'quiz'), - // phpcs:enable - capquiz_attempts_report::ALL_WITH => get_string('reportusersall', 'quiz'), - ]); - - $mform->addElement('advcheckbox', 'onlyanswered', '', get_string('reportshowonlyanswered', 'capquiz')); - $mform->addHelpButton('onlyanswered', 'reportshowonlyanswered', 'capquiz'); - } - - /** - * Adds any additional attempt fields to form - * - * @param MoodleQuickForm $mform the form to add attempt fields to - */ - protected function other_attempt_fields(MoodleQuickForm $mform) { - } - - /** - * Adds the standard preference fields to form - * - * @param MoodleQuickForm $mform the form to add preference fields to - */ - protected function standard_preference_fields(MoodleQuickForm $mform) { - $mform->addElement('text', 'pagesize', get_string('pagesize', 'quiz')); - $mform->setType('pagesize', PARAM_INT); - } - - /** - * Adds any additional preference fields to form - * - * @param MoodleQuickForm $mform the form to add preference fields to - */ - protected function other_preference_fields(MoodleQuickForm $mform) { - } -} diff --git a/report/questions/questions_form.php b/report/questions/classes/form.php similarity index 53% rename from report/questions/questions_form.php rename to report/questions/classes/form.php index e11134d..10be257 100644 --- a/report/questions/questions_form.php +++ b/report/questions/classes/form.php @@ -14,24 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * CAPQuiz questions settings form definition. - * - * @package capquizreport_questions - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace capquizreport_questions; -use mod_capquiz\report\capquiz_attempts_report; -use mod_capquiz\report\capquiz_attempts_report_form; -use MoodleQuickForm; - defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/mod/capquiz/report/attemptsreport_form.php'); +require_once($CFG->libdir . '/formslib.php'); /** * This is the settings form for the capquiz questions report. @@ -40,44 +29,26 @@ * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquizreport_questions_settings_form extends capquiz_attempts_report_form { - +class form extends \moodleform { /** * Defines the form */ protected function definition(): void { $mform = $this->_form; - $this->standard_attempt_fields($mform); - $this->other_attempt_fields($mform); - $mform->addElement('header', 'preferencesuser', get_string('reportdisplayoptions', 'quiz')); - - $this->standard_preference_fields($mform); - $this->other_preference_fields($mform); - $mform->addElement('submit', 'submitbutton', get_string('showreport', 'quiz')); - } - - /** - * Adds the standard attempt fields to form - * - * @param MoodleQuickForm $mform the form to add attempt fields to - */ - protected function standard_attempt_fields(MoodleQuickForm $mform): void { - $mform->addElement('hidden', 'attempts', capquiz_attempts_report::ALL_WITH); + $mform->addElement('hidden', 'attempts', \mod_capquiz\local\reports\options::ALL_WITH); $mform->setType('attempts', PARAM_ALPHAEXT); $mform->addElement('hidden', 'onlyanswered', 1); $mform->setType('onlyanswered', PARAM_INT); - } - /** - * Adds any additional preference fields to form - * - * @param MoodleQuickForm $mform the form to add preference fields to - */ - protected function other_preference_fields(MoodleQuickForm $mform): void { + $mform->addElement('text', 'pagesize', get_string('pagesize', 'quiz')); + $mform->setType('pagesize', PARAM_INT); + $mform->addGroup([ $mform->createElement('advcheckbox', 'qtext', '', get_string('questiontext', 'quiz_responses')), ], 'coloptions', get_string('showthe', 'quiz_responses'), [' '], false); + + $mform->addElement('submit', 'submitbutton', get_string('showreport', 'quiz')); } } diff --git a/report/questions/questions_options.php b/report/questions/classes/options.php similarity index 70% rename from report/questions/questions_options.php rename to report/questions/classes/options.php index 7a77f78..1dfd621 100644 --- a/report/questions/questions_options.php +++ b/report/questions/classes/options.php @@ -14,45 +14,47 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Class to store the options for a {@see capquiz_questions_report}. - * - * @package capquizreport_questions - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace capquizreport_questions; -use mod_capquiz\report\capquiz_attempts_report; -use mod_capquiz\report\capquiz_attempts_report_options; +use cm_info; +use mod_capquiz\capquiz; use stdClass; defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/mod/capquiz/report/attemptsreport_options.php'); +require_once($CFG->dirroot . '/mod/capquiz/classes/local/reports/options.php'); /** - * Class to store the options for a {@see capquiz_questions_report}. + * Options for the attempts report table. * * @author André Storhaug * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquizreport_questions_options extends capquiz_attempts_report_options { +class options extends \mod_capquiz\local\reports\options { /** @var bool whether to show the question text columns. */ - public $showqtext = false; + public bool $showqtext = false; /** - * @var string quiz_attempts_report::ALL_WITH or quiz_attempts_report::ENROLLED_WITH - * quiz_attempts_report::ENROLLED_WITHOUT or quiz_attempts_report::ENROLLED_ALL + * Constructor. + * + * @param capquiz $capquiz + * @param cm_info $cm + * @param stdClass $course */ - public $attempts = capquiz_attempts_report::ALL_WITH; + public function __construct(capquiz $capquiz, cm_info $cm, stdClass $course) { + parent::__construct($capquiz, $cm, $course); + $this->reporttype = 'questions'; + $this->attempts = self::ALL_WITH; + } /** * Get the current value of the settings to pass to the settings form. + * + * @return stdClass */ public function get_initial_form_data(): stdClass { $toform = parent::get_initial_form_data(); @@ -62,11 +64,12 @@ public function get_initial_form_data(): stdClass { /** * Set the fields of this object from the form data. + * * @param stdClass $fromform The data from $mform->get_data() from the settings form. */ public function setup_from_form_data(stdClass $fromform): void { parent::setup_from_form_data($fromform); - $this->showqtext = $fromform->qtext; + $this->showqtext = (bool)$fromform->qtext; } /** @@ -74,7 +77,7 @@ public function setup_from_form_data(stdClass $fromform): void { */ public function setup_from_params(): void { parent::setup_from_params(); - $this->showqtext = optional_param('qtext', $this->showqtext, PARAM_BOOL); + $this->showqtext = (bool)optional_param('qtext', $this->showqtext, PARAM_BOOL); } /** @@ -83,7 +86,7 @@ public function setup_from_params(): void { */ public function setup_from_user_preferences(): void { parent::setup_from_user_preferences(); - $this->showqtext = get_user_preferences('capquizreport_questions_qtext', $this->showqtext); + $this->showqtext = (bool)get_user_preferences('capquizreport_questions_qtext', $this->showqtext); } /** diff --git a/report/questions/classes/privacy/provider.php b/report/questions/classes/privacy/provider.php index 1b347ad..11a6c2f 100644 --- a/report/questions/classes/privacy/provider.php +++ b/report/questions/classes/privacy/provider.php @@ -14,14 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Privacy Subsystem implementation for capquizreport_questions. - * - * @package capquizreport_questions - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace capquizreport_questions\privacy; @@ -36,10 +29,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - /** - * Get the language string identifier with the component's language - * file to explain why this plugin stores no data. + * Get the language string identifier with the component's language file to explain why this plugin stores no data. * * @return string */ diff --git a/report/questions/classes/report.php b/report/questions/classes/report.php new file mode 100644 index 0000000..358581c --- /dev/null +++ b/report/questions/classes/report.php @@ -0,0 +1,148 @@ +. + +declare(strict_types=1); + +namespace capquizreport_questions; + +use cm_info; +use context_course; +use context_module; +use mod_capquiz\capquiz; +use moodle_url; +use stdClass; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/capquiz/classes/local/reports/report.php'); +require_once($CFG->dirroot . '/mod/capquiz/report/questions/classes/form.php'); +require_once($CFG->dirroot . '/mod/capquiz/report/questions/classes/options.php'); +require_once($CFG->dirroot . '/mod/capquiz/report/questions/classes/table.php'); + +/** + * The questions report provides summary information about each question in a capquiz (mainly ratings). + * + * @author André Storhaug + * @copyright 2019 Norwegian University of Science and Technology (NTNU) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class report implements \mod_capquiz\local\reports\report { + /** + * Display questions report. + * + * @param capquiz $capquiz + * @param cm_info $cm + * @param stdClass $course + * @param string $download + */ + public function display(capquiz $capquiz, cm_info $cm, stdClass $course, string $download): void { + global $DB, $OUTPUT, $PAGE; + $context = context_module::instance($cm->id); + $studentsjoins = get_enrolled_with_capabilities_join($context); + $baseurl = new moodle_url('/mod/capquiz/report.php', ['id' => $context->instanceid, 'reporttype' => 'questions']); + $form = new form($baseurl, ['capquiz' => $capquiz, 'context' => $context]); + $options = new options($capquiz, $cm, $course); + if ($fromform = $form->get_data()) { + $options->process_settings_from_form($fromform); + } else { + $options->process_settings_from_params(); + } + $form->set_data($options->get_initial_form_data()); + $questions = capquiz_report_get_questions($capquiz); + $courseshortname = format_string($course->shortname, true, ['context' => context_course::instance($course->id)]); + $table = new table($capquiz, $context, $options, $studentsjoins, $questions, $options->get_url()); + $filenamesuffix = get_string('questionsfilename', 'capquizreport_questions'); + $capquizname = format_string($capquiz->get('name')); + $filename = "$courseshortname - $capquizname - $filenamesuffix"; + $table->is_downloading($options->download, $filename, "$courseshortname $capquizname"); + if ($table->is_downloading()) { + raise_memory_limit(MEMORY_EXTRA); + } + + $hasstudents = false; + if (!empty($studentsjoins->joins)) { + $sql = "SELECT DISTINCT u.id + FROM {user} u + $studentsjoins->joins + WHERE $studentsjoins->wheres"; + $hasstudents = $DB->record_exists_sql($sql, $studentsjoins->params); + } + + $hasquestions = capquiz_has_questions($capquiz->get('id')); + if (!$table->is_downloading()) { + $PAGE->set_title($capquiz->get('name')); + $PAGE->set_heading($course->fullname); + $title = get_string('pluginname', 'capquizreport_questions') . ' ' . get_string('report', 'capquiz'); + echo $OUTPUT->heading(format_string($title, true, ['context' => context_module::instance($cm->id)])); + if (!$hasquestions) { + echo capquiz_no_questions_message($cm, $context); + } else if (!$capquiz->get('published')) { + echo capquiz_not_published_message($cm, $context); + } else if (!$hasstudents) { + echo $OUTPUT->notification(get_string('nostudentsyet')); + } + $form->display(); + } + + if ($hasquestions && !empty($questions) + && ($hasstudents || $options->attempts === \mod_capquiz\local\reports\options::ALL_WITH)) { + $table->setup_sql_queries($studentsjoins); + $columns = []; + $headers = []; + if ($table->is_downloading()) { + $columns[] = 'attemptid'; + $headers[] = get_string('attemptid', 'capquizreport_questions'); + } + $columns[] = 'questionid'; + $headers[] = get_string('questionid', 'capquiz'); + $columns[] = 'questionrating'; + $headers[] = get_string('questionrating', 'capquiz'); + $columns[] = 'questionprevrating'; + $headers[] = get_string('questionprevrating', 'capquizreport_questions'); + if ($table->is_downloading()) { + $columns[] = 'questionprevratingmanual'; + $headers[] = get_string('questionprevratingmanual', 'capquizreport_questions'); + } + $columns[] = 'moodlequestionid'; + $headers[] = get_string('moodlequestionid', 'capquiz'); + if ($options->showqtext) { + $columns[] = 'question'; + $headers[] = get_string('question'); + } + $columns[] = 'timecreated'; + $headers[] = get_string('timecreated'); + + $table->define_columns($columns); + $table->define_headers($headers); + $table->sortable(true, 'uniqueidquestion'); + $table->define_baseurl($options->get_url()); + $table->column_suppress('picture'); + $table->column_suppress('fullname'); + foreach (\core_user\fields::for_identity($context)->get_required_fields() as $field) { + $table->column_suppress($field); + } + $table->column_class('picture', 'picture'); + $table->column_class('lastname', 'bold'); + $table->column_class('firstname', 'bold'); + $table->column_class('fullname', 'bold'); + $table->no_sorting('answerstate'); + $table->no_sorting('question'); + $table->set_attribute('id', 'responses'); + $table->collapsible(true); + $table->out($options->pagesize, true); + } + } +} diff --git a/report/questions/questions_table.php b/report/questions/classes/table.php similarity index 72% rename from report/questions/questions_table.php rename to report/questions/classes/table.php index 6f39675..f6a4c4d 100644 --- a/report/questions/questions_table.php +++ b/report/questions/classes/table.php @@ -14,31 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file defines the capquiz questions table for showing question data. - * - * @package capquizreport_questions - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +declare(strict_types=1); namespace capquizreport_questions; use core\context; use core\dml\sql_join; -use mod_capquiz\report\capquiz_attempts_report; -use mod_capquiz\report\capquiz_attempts_report_options; -use mod_capquiz\report\capquiz_attempts_report_table; +use mod_capquiz\capquiz; use moodle_url; -use quiz_responses_options; use stdClass; defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/mod/capquiz/report/attemptsreport_table.php'); require_once($CFG->dirroot . '/mod/quiz/locallib.php'); - +require_once($CFG->dirroot . '/mod/capquiz/classes/local/reports/table.php'); /** * This is a table subclass for displaying the capquiz question report. @@ -47,40 +36,36 @@ * @copyright 2019 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class capquizreport_questions_table extends capquiz_attempts_report_table { - +class table extends \mod_capquiz\local\reports\table { /** * Constructor. * - * @param object $capquiz + * @param capquiz $capquiz * @param context $context - * @param capquiz_attempts_report_options $options + * @param options $options * @param sql_join $studentsjoins * @param array $questions * @param moodle_url $reporturl */ - public function __construct($capquiz, $context, capquiz_attempts_report_options $options, - sql_join $studentsjoins, $questions, $reporturl) { - parent::__construct('mod-capquiz-report-questions-report', $capquiz, $context, - $options, $studentsjoins, $questions, $reporturl); + public function __construct(capquiz $capquiz, context $context, options $options, sql_join $studentsjoins, + array $questions, moodle_url $reporturl) { + parent::__construct('questions', $capquiz, $context, $options, $studentsjoins, $questions, $reporturl); } /** * Take the data returned from the db_query and go through all the rows * processing each col using either col_{columnname} method or other_cols - * method or if other_cols returns NULL then put the data straight into the - * table. + * method or if other_cols returns NULL then put the data straight into the table. * * This overwrites the parent method because full SQL query may fail on Mysql * because of the limit in the number of tables in the join. Therefore we only * join 59 tables in the main query and add the rest here. */ public function build_table(): void { - if (!$this->rawdata) { - return; + if ($this->rawdata) { + $this->strtimeformat = str_replace(',', ' ', get_string('strftimedatetimeseconds', 'capquiz')); + parent::build_table(); } - $this->strtimeformat = str_replace(',', ' ', get_string('strftimedatetimeseconds', 'capquiz')); - parent::build_table(); } /** @@ -91,7 +76,7 @@ public function build_table(): void { */ public function other_cols($colname, $attempt): ?string { return match ($colname) { - 'question' => $this->data_col($attempt->slot, 'questionsummary', $attempt), + 'question' => $this->data_col((int)$attempt->slot, 'questionsummary', $attempt), default => null, }; } @@ -108,7 +93,7 @@ public function data_col(int $slot, string $field, stdClass $attempt): string { return '-'; } $value = $this->field_from_extra_data($attempt, $slot, $field); - $summary = $value !== null ? trim($value) : '-'; + $summary = empty($value) ? '-' : trim($value); if ($this->is_downloading() && $this->is_downloading() != 'html') { return $summary; } @@ -153,9 +138,9 @@ public function col_answerstate(stdClass $attempt): string { } $state = $this->slot_state($attempt, $attempt->slot); if ($this->is_downloading()) { - return $state->__toString(); + return (string)$state; } else { - return $this->make_review_link($state, $attempt, $attempt->slot); + return $this->make_review_link((string)$state, $attempt, $attempt->slot); } } @@ -267,44 +252,38 @@ public function col_questionprevratingmanual(stdClass $attempt): string { public function base_sql(sql_join $allowedstudentsjoins): array { global $DB; list($fields, $from, $where, $params) = parent::base_sql($allowedstudentsjoins); - $fields1 = ', - 1 AS identifier, - ca.time_answered AS timecreated, - cqr.rating AS questionrating, - pcqr.rating AS questionprevrating, - pcqr.manual AS manualprevqrating, - cq2.id AS questionid, - cq2.question_id AS moodlequestionid'; - - $from1 = "\nJOIN {capquiz_question_rating} cqr ON cqr.id = ca.question_rating_id"; - $from1 .= "\nJOIN {capquiz_question_rating} pcqr ON pcqr.id = ca.question_prev_rating_id"; - $from1 .= "\nJOIN {capquiz_question} cq2 ON cq2.id = cqr.capquiz_question_id"; - - $sql1 = "SELECT {$fields}{$fields1} - \nFROM ({$from}{$from1}) - \nWHERE {$where}"; - - $fields2 = ', - 2 AS identifier, - ca.time_answered AS timecreated, - cqr.rating AS questionrating, - pcqr.rating AS questionprevrating, - pcqr.manual AS manualprevqrating, - cq2.id AS questionid, - cq2.question_id AS moodlequestionid'; - $from2 = "\nJOIN {capquiz_question_rating} cqr ON cqr.id = ca.prev_question_rating_id"; - $from2 .= "\nJOIN {capquiz_question_rating} pcqr ON pcqr.id = ca.prev_question_prev_rating_id"; - $from2 .= "\nJOIN {capquiz_question} cq2 ON cq2.id = cqr.capquiz_question_id"; + $sql1 = "SELECT $fields, + 1 AS identifier, + ca.time_answered AS timecreated, + cqr.rating AS questionrating, + pcqr.rating AS questionprevrating, + pcqr.manual AS manualprevqrating, + cq2.id AS questionid, + cq2.question_id AS moodlequestionid + FROM $from + JOIN {capquiz_question_rating} cqr ON cqr.id = ca.question_rating_id + JOIN {capquiz_question_rating} pcqr ON pcqr.id = ca.question_prev_rating_id + JOIN {capquiz_question} cq2 ON cq2.id = cqr.capquiz_question_id + WHERE $where"; - $sql2 = "SELECT {$fields}{$fields2} - \nFROM {$from}{$from2} - \nWHERE {$where}"; + $sql2 = "SELECT $fields, + 2 AS identifier, + ca.time_answered AS timecreated, + cqr.rating AS questionrating, + pcqr.rating AS questionprevrating, + pcqr.manual AS manualprevqrating, + cq2.id AS questionid, + cq2.question_id AS moodlequestionid + FROM $from + JOIN {capquiz_question_rating} cqr ON cqr.id = ca.prev_question_rating_id + JOIN {capquiz_question_rating} pcqr ON pcqr.id = ca.prev_question_prev_rating_id + JOIN {capquiz_question} cq2 ON cq2.id = cqr.capquiz_question_id + WHERE $where"; $fields = 'DISTINCT ' . $DB->sql_concat('userid', "'#'", 'COALESCE(attempt, 0)', "'#'", 'identifier') - . ' AS uniqueidquestion,'; - $fields .= "ratings.*"; - $from = "(\n{$sql1} \nUNION ALL\n {$sql2}) AS ratings"; + . ' AS uniqueidquestion, ratings.*'; + $from = "($sql1 UNION ALL $sql2) AS ratings"; list($from, $params) = uniquify_sql_params($from, $params); return [$fields, $from, '1=1', $params]; diff --git a/report/questions/lang/en/capquizreport_questions.php b/report/questions/lang/en/capquizreport_questions.php index fc08a56..38c443f 100644 --- a/report/questions/lang/en/capquizreport_questions.php +++ b/report/questions/lang/en/capquizreport_questions.php @@ -27,29 +27,18 @@ $string['answerstate'] = 'Answer state'; $string['attemptid'] = 'Attempt id'; -$string['correct'] = 'Correct'; - -$string['false'] = 'False'; - $string['pluginname'] = 'Questions'; -$string['privacy:metadata'] = 'The capquizreport questions plugin does not store any personal data.'; +$string['privacy:metadata'] = 'The CAPQuiz questions report plugin does not store any personal data.'; $string['qprevrating'] = 'question\'s previous rating'; $string['qrating'] = 'question rating'; -$string['question'] = 'Question'; $string['questionprevrating'] = 'Question\'s previous rating'; $string['questionprevratingmanual'] = 'Previous rating manually updated'; $string['questionsfilename'] = 'questions'; $string['rating_manually_updated'] = 'Previous rating was manually updated'; $string['response'] = 'Response'; -$string['rightanswer'] = 'Right answer'; - -$string['timecreated'] = 'Time created'; -$string['true'] = 'True'; $string['uprevrating'] = 'user\'s previous rating'; $string['urating'] = 'user rating'; $string['userprevrating'] = 'User\'s previous rating'; - -$string['wrong'] = 'Wrong'; diff --git a/report/questions/report.php b/report/questions/report.php deleted file mode 100644 index 6305e22..0000000 --- a/report/questions/report.php +++ /dev/null @@ -1,223 +0,0 @@ -. - -/** - * CAPQuiz questions report class. - * - * @package capquizreport_questions - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace capquizreport_questions; - -use context_course; -use mod_capquiz\capquiz; -use mod_capquiz\report\capquiz_attempts_report; -use mod_capquiz\report\capquiz_attempts_report_options; -use stdClass; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/capquiz/report/attemptsreport.php'); -require_once(__DIR__ . '/questions_form.php'); -require_once(__DIR__ . '/questions_table.php'); -require_once(__DIR__ . '/questions_options.php'); - -/** - * The capquiz questions report provides summary information about the questions in a capquiz (mainly ratings). - * - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquizreport_questions_report extends capquiz_attempts_report { - - /** - * Displays the full report. - * - * @param capquiz $capquiz - * @param stdClass $cm - * @param stdClass $course - * @param string $download type of download being requested - */ - public function display($capquiz, $cm, $course, $download): bool { - global $DB; - - list($studentsjoins) = $this->init( - 'questions', 'capquizreport_questions\capquizreport_questions_settings_form', $capquiz, $cm, $course); - - $this->options = new capquizreport_questions_options('questions', $capquiz, $cm, $course); - - if ($fromform = $this->form->get_data()) { - $this->options->process_settings_from_form($fromform); - - } else { - $this->options->process_settings_from_params(); - } - - $this->form->set_data($this->options->get_initial_form_data()); - - // Load the required questions. - $questions = capquiz_report_get_questions($capquiz); - - // Prepare for downloading, if applicable. - $courseshortname = format_string($course->shortname, true, ['context' => context_course::instance($course->id)]); - - $table = new capquizreport_questions_table($capquiz, $this->context, - $this->options, $studentsjoins, $questions, $this->options->get_url()); - - $filename = capquiz_report_download_filename(get_string('questionsfilename', 'capquizreport_questions'), - $courseshortname, $capquiz->name()); - - $table->is_downloading($this->options->download, $filename, $courseshortname . ' ' . format_string($capquiz->name())); - if ($table->is_downloading()) { - raise_memory_limit(MEMORY_EXTRA); - } - - $hasstudents = false; - if (!empty($studentsjoins->joins)) { - $sql = "SELECT DISTINCT u.id - FROM {user} u - $studentsjoins->joins - WHERE $studentsjoins->wheres"; - $hasstudents = $DB->record_exists_sql($sql, $studentsjoins->params); - } - - $hasquestions = capquiz_has_questions($capquiz->id()); - // Start output. - if (!$table->is_downloading()) { - // Only print headers if not asked to download data. - $this->print_standard_header_and_messages($cm, $course, $capquiz, $this->options, $hasquestions, $hasstudents); - - // Print the display options. - $this->form->display(); - } - - if ($hasquestions && !empty($questions) && ($hasstudents || $this->options->attempts == self::ALL_WITH)) { - - $table->setup_sql_queries($studentsjoins); - - // Define table columns. - $columns = []; - $headers = []; - - if ($table->is_downloading()) { - $columns[] = 'attemptid'; - $headers[] = get_string('attemptid', 'capquizreport_questions'); - } - - $this->add_questionid_column($columns, $headers); - $this->add_question_rating_columns($columns, $headers); - - if ($table->is_downloading()) { - $columns[] = 'questionprevratingmanual'; - $headers[] = get_string('questionprevratingmanual', 'capquizreport_questions'); - } - - $this->add_moodlequestionid_column($columns, $headers); - - if ($this->options->showqtext) { - $columns[] = 'question'; - $headers[] = get_string('question', 'capquizreport_questions'); - } - - $columns[] = 'timecreated'; - $headers[] = get_string('timecreated', 'capquizreport_questions'); - - $table->define_columns($columns); - $table->define_headers($headers); - $table->sortable(true, 'uniqueidquestion'); - - // Set up the table. - $table->define_baseurl($this->options->get_url()); - - $this->configure_user_columns($table); - - $table->no_sorting('answerstate'); - $table->no_sorting('question'); - - $table->set_attribute('id', 'responses'); - - $table->collapsible(true); - - $table->out($this->options->pagesize, true); - } - return true; - } - - /** - * Outputs the things you commonly want at the top of a capquiz report. - * - * Calls through to {@see print_header_and_tabs()} and then - * outputs the standard group selector, number of attempts summary, - * and messages to cover common cases when the report can't be shown. - * - * @param stdClass $cm the course_module information. - * @param stdClass $course the course settings. - * @param capquiz $capquiz the capquiz settings. - * @param capquiz_attempts_report_options $options the current report settings. - * @param bool $hasquestions whether there are any questions in the capquiz. - * @param bool $hasstudents whether there are any relevant students. - */ - protected function print_standard_header_and_messages($cm, $course, capquiz $capquiz, - capquiz_attempts_report_options $options, - bool $hasquestions, bool $hasstudents): void { - global $OUTPUT; - $this->print_header_and_tabs($cm, $course, $capquiz, $this->mode); - if (!$hasquestions) { - echo capquiz_no_questions_message($capquiz, $cm, $this->context); - } else if (!$capquiz->is_published()) { - echo capquiz_not_published_message($capquiz, $cm, $this->context); - } else if (!$hasstudents) { - echo $OUTPUT->notification(get_string('nostudentsyet')); - } - } - - /** - * Adds column with question rating - * - * @param array $columns columns to be added - * @param array $headers column headers - */ - protected function add_question_rating_columns(array &$columns, array &$headers): void { - $this->add_question_rating_column($columns, $headers); - $this->add_question_previous_rating_column($columns, $headers); - } - - /** - * Adds column with question rating - * - * @param array $columns columns to be added - * @param array $headers column headers - */ - protected function add_question_rating_column(array &$columns, array &$headers): void { - $columns[] = 'questionrating'; - $headers[] = get_string('questionrating', 'capquiz'); - } - - /** - * Adds column with previous question ratings - * - * @param array $columns columns to be added - * @param array $headers column headers - */ - protected function add_question_previous_rating_column(array &$columns, array &$headers): void { - $columns[] = 'questionprevrating'; - $headers[] = get_string('questionprevrating', 'capquizreport_questions'); - } -} diff --git a/report/questions/version.php b/report/questions/version.php index b0a7313..da5f3a7 100644 --- a/report/questions/version.php +++ b/report/questions/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024100700; +$plugin->version = 2024102101; $plugin->requires = 2022041901; $plugin->component = 'capquizreport_questions'; $plugin->maturity = MATURITY_STABLE; -$plugin->release = '1.1.0'; +$plugin->release = '1.2.0'; diff --git a/report/report.php b/report/report.php deleted file mode 100644 index 61ac4fd..0000000 --- a/report/report.php +++ /dev/null @@ -1,85 +0,0 @@ -. - -/** - * Base class for capquiz report plugins. - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\report; - -use context_module; -use mod_capquiz\capquiz; -use stdClass; - -/** - * Base class for capquiz report plugins. - * - * Doesn't do anything on it's own -- it needs to be extended. - * This class displays capquiz reports. - * - * This file can refer to itself as report.php to pass variables - * to itself - all these will also be globally available. - * - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class report { - /** - * Displays the full report. - * - * @param capquiz $capquiz - * @param stdClass $cm - * @param stdClass $course - * @param string $download type of download being requested - */ - public function display(capquiz $capquiz, stdClass $cm, stdClass $course, string $download): bool { - // This function renders the html for the report. - return true; - } - - /** - * Allows the plugin to control who can see this plugin. - * - * @param context_module $contextmodule - */ - public function canview(context_module $contextmodule): bool { - return true; - } - - /** - * Initialise some parts of $PAGE and start output. - * - * @param stdClass $cm the course_module information. - * @param stdClass $course the course settings. - * @param capquiz $capquiz the capquiz settings. - * @param string $reportmode the report name. - */ - public function print_header_and_tabs(stdClass $cm, stdClass $course, capquiz $capquiz, string $reportmode = 'attempts'): void { - global $PAGE, $OUTPUT; - $PAGE->set_title($capquiz->name()); - $PAGE->set_heading($course->fullname); - $context = context_module::instance($cm->id); - echo $OUTPUT->heading(format_string( - get_string('pluginname', 'capquizreport_' . $reportmode) . ' ' . get_string('report', 'capquiz'), - true, ['context' => $context])); - } -} diff --git a/report/reportfactory.php b/report/reportfactory.php deleted file mode 100644 index 8a49ab5..0000000 --- a/report/reportfactory.php +++ /dev/null @@ -1,75 +0,0 @@ -. - -/** - * Capquiz report factory. Provides a convenient way to create an capquiz report of any type. - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz\report; - -use capquiz_exception; - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/capquiz/locallib.php'); - -/** - * Capquiz report factory. Provides a convenient way to create an capquiz report of any type. - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class capquiz_report_factory { - - /** - * Create a capquiz report of a given type and return it. - * - * @param string $type the required type. - */ - public static function make(string $type): capquiz_attempts_report { - $class = self::class_for_type($type); - return new $class(); - } - - /** - * The class name corresponding to a report type. - * - * @param string $type report type name. - */ - protected static function class_for_type(string $type): string { - global $CFG; - $typelc = strtolower($type); - $file = $CFG->dirroot . '/mod/capquiz/report/' . $type . '/report.php'; - $class = "capquizreport_{$typelc}\\capquizreport_{$typelc}_report"; - if (!is_readable($file)) { - throw new capquiz_exception('capquiz_report_factory: unknown report type ' . $type); - } - include_once($file); - - if (!class_exists($class)) { - throw new capquiz_exception('capquiz_report_factory: report type ' . $type . - ' does not define the expected class ' . $class); - } - return $class; - } -} diff --git a/report/reportlib.php b/report/reportlib.php index f7480e6..3759479 100644 --- a/report/reportlib.php +++ b/report/reportlib.php @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +declare(strict_types=1); + /** * Helper functions for the capquiz reports. * @@ -25,8 +27,6 @@ use core\context\module; use mod_capquiz\capquiz; -use mod_capquiz\capquiz_urls; -use mod_capquiz\report\capquiz_report_factory; defined('MOODLE_INTERNAL') || die(); @@ -34,42 +34,6 @@ require_once($CFG->dirroot . '/mod/capquiz/lib.php'); require_once($CFG->libdir . '/filelib.php'); -/** - * Generates and returns list of available CAPQuiz report sub-plugins - * - * @param context_module $context the context level to check caps against - * @return array list of valid reports present - */ -function capquiz_report_list(context_module $context): array { - static $reportlist; - if (!empty($reportlist)) { - return $reportlist; - } - $pluginmanager = new capquiz_plugin_manager('capquizreport'); - $enabledplugins = core_plugin_manager::instance()->get_enabled_plugins('capquizreport'); - foreach ($pluginmanager->get_sorted_plugins_list() as $reportname) { - $report = capquiz_report_factory::make($reportname); - if (isset($enabledplugins[$reportname]) && $report->canview($context)) { - $reportlist[] = $reportname; - } - } - return $reportlist; -} - -/** - * Create a filename for use when downloading data from a capquiz report. It is - * expected that this will be passed to flexible_table::is_downloading, which - * cleans the filename of bad characters and adds the file extension. - * - * @param string $report the type of report. - * @param string $courseshortname the course shortname. - * @param string $capquizname the capquiz name. - * @return string the filename. - */ -function capquiz_report_download_filename(string $report, string $courseshortname, string $capquizname): string { - return $courseshortname . '-' . format_string($capquizname) . '-' . $report; -} - /** * Are there any questions in this capquiz? * @@ -94,23 +58,21 @@ function capquiz_has_questions(int $capquizid): bool { function capquiz_report_get_questions(capquiz $capquiz): array { global $DB; $sql = 'SELECT DISTINCT ' . $DB->sql_concat('qa.id', "'#'", 'cu.id', 'ca.slot') . ' AS uniqueid, - ca.slot, - q.id, - q.qtype, - q.length - FROM {question} q - JOIN {capquiz_question} cq ON cq.question_id = q.id - JOIN {capquiz_question_list} cql ON cql.id = cq.question_list_id AND cql.is_template = 0 - JOIN {capquiz_user} cu ON cu.capquiz_id = cql.capquiz_id - JOIN {question_usages} qu ON qu.id = cu.question_usage_id - JOIN {question_attempts} qa ON qa.questionusageid = qu.id - JOIN {capquiz_attempt} ca ON ca.question_id = cq.id AND ca.slot = qa.slot AND ca.user_id = cu.id - - WHERE cu.capquiz_id = ? - AND q.length > 0 - - ORDER BY ca.slot'; - $qsbyslot = $DB->get_records_sql($sql, [$capquiz->id()]); + ca.slot, + q.id, + q.qtype, + q.length + FROM {question} q + JOIN {capquiz_question} cq ON cq.question_id = q.id + JOIN {capquiz_question_list} cql ON cql.id = cq.question_list_id AND cql.is_template = 0 + JOIN {capquiz_user} cu ON cu.capquiz_id = cql.capquiz_id + JOIN {question_usages} qu ON qu.id = cu.question_usage_id + JOIN {question_attempts} qa ON qa.questionusageid = qu.id + JOIN {capquiz_attempt} ca ON ca.question_id = cq.id AND ca.slot = qa.slot AND ca.user_id = cu.id + WHERE cu.capquiz_id = ? + AND q.length > 0 + ORDER BY ca.slot'; + $qsbyslot = $DB->get_records_sql($sql, [$capquiz->get('id')]); $number = 1; foreach ($qsbyslot as $question) { $question->number = $number; @@ -120,30 +82,13 @@ function capquiz_report_get_questions(capquiz $capquiz): array { return $qsbyslot; } -/** - * Return a textual summary of the number of attempts that have been made at a particular quiz, - * returns '' if no attempts have been made yet, unless $returnzero is passed as true. - * - * @param capquiz $capquiz - * @param bool $returnzero if false (default), when no attempts have been - * made '' is returned instead of 'Attempts: 0'. - * @return string a string like "Attempts: 123". - */ -function capquiz_num_attempt_summary(capquiz $capquiz, bool $returnzero = false): string { - $numattempts = capquiz_report_num_attempt($capquiz); - if ($numattempts || $returnzero) { - return get_string('attemptsnum', 'quiz', $numattempts); - } - return ''; -} - /** * Returns the number of CAPQuiz attempts. * - * @param capquiz $capquiz + * @param int $capquizid * @return int number of answered CAPQuiz attempts */ -function capquiz_report_num_attempt(capquiz $capquiz): int { +function capquiz_report_num_attempt(int $capquizid): int { global $DB; $sql = 'SELECT COUNT(ca.id) FROM {capquiz_attempt} ca @@ -151,23 +96,23 @@ function capquiz_report_num_attempt(capquiz $capquiz): int { JOIN {question_usages} qu ON qu.id = cu.question_usage_id JOIN {question_attempts} qa ON qa.questionusageid = qu.id AND qa.slot = ca.slot JOIN {capquiz_question} cq ON cq.id = ca.question_id'; - return $DB->count_records_sql($sql, ['capquizid' => $capquiz->id()]); + return $DB->count_records_sql($sql, ['capquizid' => $capquizid]); } /** * Generate a message saying that this capquiz has no questions, with a button to * go to the edit page, if the user has the right capability. * - * @param object $quiz the quiz settings. - * @param object $cm the course_module object. + * @param cm_info $cm the course_module object. * @param module $context the quiz context. * @return string HTML to output. */ -function capquiz_no_questions_message($quiz, $cm, module $context): string { +function capquiz_no_questions_message(cm_info $cm, module $context): string { global $OUTPUT; $output = $OUTPUT->notification(get_string('noquestions', 'quiz')); if (has_capability('mod/capquiz:manage', $context)) { - $output .= $OUTPUT->single_button(capquiz_urls::view_question_list_url(), get_string('editquiz', 'quiz'), 'get'); + $url = new moodle_url('/mod/capquiz/view.php', ['id' => $cm->id, 'page' => 'questions']); + $output .= $OUTPUT->single_button($url, get_string('editquiz', 'quiz'), 'get'); } return $output; } @@ -176,16 +121,16 @@ function capquiz_no_questions_message($quiz, $cm, module $context): string { * Generate a message saying that this capquiz has no questions, with a button to * go to the dashboard page (question list settings), if the user has the right capability. * - * @param object $quiz the quiz settings. - * @param object $cm the course_module object. + * @param cm_info $cm the course_module object. * @param module $context the quiz context. * @return string HTML to output. */ -function capquiz_not_published_message($quiz, $cm, module $context): string { +function capquiz_not_published_message(cm_info $cm, module $context): string { global $OUTPUT; $output = $OUTPUT->notification(get_string('question_list_not_published', 'capquiz')); if (has_capability('mod/capquiz:manage', $context)) { - $output .= $OUTPUT->single_button(capquiz_urls::view_url(), get_string('question_list_settings', 'capquiz'), 'get'); + $viewurl = new moodle_url('/mod/capquiz/view.php', ['id' => $cm->id]); + $output .= $OUTPUT->single_button($viewurl, get_string('question_list_settings', 'capquiz'), 'get'); } return $output; } diff --git a/settings.php b/settings.php index 6a91d24..0df316a 100644 --- a/settings.php +++ b/settings.php @@ -26,14 +26,12 @@ require_once($CFG->dirroot . '/mod/capquiz/adminlib.php'); -$modcapquizfolder = new admin_category('modcapquizfolder', - new lang_string('pluginname', 'capquiz'), $module->is_enabled() === false); +global $ADMIN; +$modcapquizfolder = new admin_category('modcapquizfolder', new lang_string('pluginname', 'capquiz'), !$module->is_enabled()); $ADMIN->add('modsettings', $modcapquizfolder); -$settings = new admin_settingpage($section, - get_string('settings', 'capquiz'), 'moodle/site:config', !$module->is_enabled()); - +$settings = new admin_settingpage($section, get_string('settings', 'capquiz'), 'moodle/site:config', !$module->is_enabled()); $ADMIN->add('modcapquizfolder', $settings); // Tell core we already added the settings structure. diff --git a/styles.css b/styles.css index f78332d..aab61d6 100755 --- a/styles.css +++ b/styles.css @@ -1,56 +1,3 @@ -.capquiz-list { - border: 1px solid #eaeaea; - padding: 0; -} - -.capquiz-list li { - padding: 4px; - background: #fafafa; - border-bottom: 1px solid #eaeaea; -} - -.capquiz-question-list li { - display: flex; -} - -.capquiz-flex { - display: flex; -} - -.capquiz-flex-item { - width: 50%; -} - -.capquiz-tree tr td, -.capquiz-tree tr th { - padding: 6px; -} - -.capquiz-tree tr:first-child { - background: #fafafa; -} - -.capquiz-tree tr:first-child td { - font-weight: bold; -} - -.capquiz-attempt-metainfo { - width: 100%; - background: #f7f7f7; - padding: 8px; - margin: 8px 0; - border: 1px solid #e0e0e0; - overflow: hidden; -} - -.capquiz-attempt-metainfo tr:first-child { - background: #fafafa; -} - -.capquiz-qlist-questions tr td { - padding: 2px 4px; -} - .capquiz-quiz-top { background: #f7f7f7; padding: 8px; @@ -89,10 +36,6 @@ } } -.capquiz-reduced-width { - width: 80%; -} - .capquiz-quiz-progress-fill { background: #59f; text-align: left; @@ -141,37 +84,6 @@ border: 1px solid #bbb; } -.capquiz-default-question-rating { - display: inline-block; -} - -.capquiz-sortable { - cursor: pointer; - user-select: none; -} - -.capquiz-details { - background: #f7f7f7; - border: 1px solid #e0e0e0; - transition: 0.2s ease all; -} - -.capquiz-details:hover { - background: #f0f0f0; - border: 1px solid #ddd; -} - -.capquiz-details summary { - outline: none; - user-select: none; - padding: 8px; -} - -.capquiz-details-content { - background: #fdfdfd; - padding: 8px; -} - .capquiz-quiz-stars { position: relative; } @@ -183,43 +95,6 @@ cursor: pointer; } -.capquiz-star-tooltip { - position: fixed; - padding: 8px; - border: 1px solid #555; - background: #333; - color: #dbdbdb; - display: none; - z-index: 1000; - border-radius: 6px; - max-width: 600px; -} - -.capquiz-question-rating { - width: 112px; -} - -.capquiz-question-rating input { - width: 80px; -} - -.capquiz-classlist tr td, -.capquiz-classlist tr th { - padding: 4px 16px; -} - -.capquiz-classlist tr:nth-child(odd) td { - background: #fafafa; -} - -.capquiz-add-selected-questions { - margin: 4px; -} - -.capquiz-regrade-all { - margin-top: 16px; -} - #page-mod-capquiz-edit table.question-bank-table { width: 100%; } diff --git a/templates/button.mustache b/templates/button.mustache deleted file mode 100755 index f685762..0000000 --- a/templates/button.mustache +++ /dev/null @@ -1,31 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/button - - Example context (json): - { - "method": "post", - "url": "", - "primary": true, - "tooltip": "A button.", - "label": "Button" - } -}} -{{#button}} - {{>core/single_button}} -{{/button}} diff --git a/templates/classlist.mustache b/templates/classlist.mustache index 5b5a5d8..ba30497 100755 --- a/templates/classlist.mustache +++ b/templates/classlist.mustache @@ -21,7 +21,6 @@ { "users": [ { - "index": 1, "username": "sebastsg", "firstname": "Sebastian", "lastname": "Gundersen", @@ -43,21 +42,19 @@ } }}

{{#str}} classlist, capquiz {{/str}}

- +
- - - - - - - - + + + + + + + {{#users}} {{#.}} - @@ -72,11 +69,9 @@ {{/users}}
#{{#str}} username, capquiz {{/str}}{{#str}} firstname, capquiz {{/str}}{{#str}} lastname, capquiz {{/str}}{{#str}} rating, capquiz {{/str}}{{#str}} stars, capquiz {{/str}}{{#str}} graded_stars, capquiz {{/str}}{{#str}} pass_or_fail, capquiz {{/str}}{{#str}} username, capquiz {{/str}}{{#str}} firstname, capquiz {{/str}}{{#str}} lastname, capquiz {{/str}}{{#str}} rating, capquiz {{/str}}{{#str}} stars, capquiz {{/str}}{{#str}} graded_stars, capquiz {{/str}}{{#str}} pass_or_fail, capquiz {{/str}}
{{ index }} {{ username }} {{ firstname }} {{ lastname }}
- {{^users}}

{{#str}} no_enrolled_students, capquiz {{/str}}

{{/users}} - {{#regrade}} - {{> core/single_button}} + {{>core/single_button}} {{/regrade}} diff --git a/templates/configure_grading.mustache b/templates/configure_grading.mustache deleted file mode 100644 index 4f41bc7..0000000 --- a/templates/configure_grading.mustache +++ /dev/null @@ -1,26 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/configure_badge_rating - - Example context (json): - { - "form": "raw html for the form" - } -}} -

{{#str}} configure_grading, capquiz {{/str}}

-{{{ rating_form }}} diff --git a/templates/create_question_list.mustache b/templates/create_question_list.mustache deleted file mode 100755 index bb924e4..0000000 --- a/templates/create_question_list.mustache +++ /dev/null @@ -1,26 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/create_question_list - - Example context (json): - { - "form": "raw html for the form" - } -}} -

{{#str}} create_question_list, capquiz {{/str}}

-{{{ form }}} diff --git a/templates/merge_with_question_list.mustache b/templates/import_page.mustache similarity index 72% rename from templates/merge_with_question_list.mustache rename to templates/import_page.mustache index 057449e..596cb2c 100644 --- a/templates/merge_with_question_list.mustache +++ b/templates/import_page.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template mod_capquiz/merge_with_question_list + @template mod_capquiz/import_page Example context (json): { @@ -35,36 +35,33 @@ } }}

{{#str}}question_list, capquiz{{/str}}

- {{#lists}} -
+
{{ title }} - - {{#userdate}}{{time_created}}, %A, %d %B %Y, %I:%M %p{{/userdate}} - + {{#userdate}}{{ timecreated }}, %A, %d %B %Y, %I:%M %p{{/userdate}} -
+

{{ description }}

- {{#merge}} + {{#merge}} + {{>core/single_button}} + {{/merge}} + {{#delete}} + {{>core/single_button}} - {{/merge}} - {{#delete}} - - {{>core/single_button}} - - {{/delete}} - + + {{/delete}} +
- {{#questions}} - + {{#questions}} + - {{/questions}} + {{/questions}}
{{#str}} rating, capquiz {{/str}} {{#str}} name, capquiz {{/str}}
{{ rating }} {{ name }}
diff --git a/templates/instructor_dashboard_summary.mustache b/templates/instructor_dashboard.mustache old mode 100755 new mode 100644 similarity index 50% rename from templates/instructor_dashboard_summary.mustache rename to templates/instructor_dashboard.mustache index e333910..31ae541 --- a/templates/instructor_dashboard_summary.mustache +++ b/templates/instructor_dashboard.mustache @@ -15,51 +15,52 @@ along with Moodle. If not, see . }} {{! - @template mod_capquiz/instructor_dashboard_summary + @template mod_capquiz/instructor_dashboard Example context (json): { "published_status": "Published", - "question_list_title": "My question list", "question_count": 15, - "enrolled_student_count": 40 + "enrolled_student_count": 40, + "publishing": { + "publishbutton": { + "id": "id", + "method": "post", + "url": "#", + "primary": true, + "label": "Publish" + }, + "message": "" + } } }}

{{#str}} dashboard, capquiz {{/str}}

- +
- - + + - - + + - - - - - - + +
- {{#str}} status, capquiz {{/str}}: - - {{{published_status}}} - {{#str}} status, capquiz {{/str}}:{{{published_status}}}
- {{#str}} question_list, capquiz {{/str}}: - - {{{question_list_title}}} - {{#str}} question_count, capquiz {{/str}}:{{{question_count}}}
- {{#str}} question_count, capquiz {{/str}}: - - {{{question_count}}} -
- {{#str}} enrolled_students, capquiz {{/str}}: - - {{{enrolled_student_count}}} - {{#str}} enrolled_students, capquiz {{/str}}:{{{enrolled_student_count}}}

+ +{{#publishing}} +

{{#str}} publish, capquiz {{/str}}

+ {{#str}} publish_explanation, capquiz {{/str}} + {{#publishbutton}} + {{>core/single_button}} + {{/publishbutton}} +
+ {{#message}} + {{{message}}} + {{/message}} +{{/publishing}} diff --git a/templates/instructor_dashboard_publish.mustache b/templates/instructor_dashboard_publish.mustache deleted file mode 100755 index 6df8fa1..0000000 --- a/templates/instructor_dashboard_publish.mustache +++ /dev/null @@ -1,44 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/instructor_dashboard_publish - - Example context (json): - { - "publish": { - "id": "id", - "method": "post", - "url": "#", - "primary": true, - "label": "Publish" - }, - "message": "" - } -}} -

{{#str}} publish, capquiz {{/str}}

- -{{#str}} publish_explanation, capquiz {{/str}} - -{{#publish}} - {{>core/single_button}} -{{/publish}} - -
- -{{#message}} - {{{message}}} -{{/message}} diff --git a/templates/instructor_dashboard_template.mustache b/templates/instructor_dashboard_template.mustache deleted file mode 100755 index 664517d..0000000 --- a/templates/instructor_dashboard_template.mustache +++ /dev/null @@ -1,45 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/instructor_dashboard_template - - Example context (json): - { - "create_template": { - "id": "id", - "primary": true, - "method": "post", - "url": "#", - "label": "Create template", - "disabled": false - }, - "message": "" - } -}} -

{{#str}} template, capquiz {{/str}}

- -{{#str}}template_explanation, capquiz {{/str}} - -{{#create_template}} - {{>core/single_button}} -{{/create_template}} - -
- -{{#message}} - {{{message}}} -{{/message}} diff --git a/templates/matchmaking_configuration.mustache b/templates/matchmaking_configuration.mustache deleted file mode 100755 index 3e52043..0000000 --- a/templates/matchmaking_configuration.mustache +++ /dev/null @@ -1,27 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/matchmaking_configuration - - Example context (json): - { - "strategy": "N-closest", - "form": "raw html for the form" - } -}} -

{{#str}} configure, capquiz {{/str}} {{strategy}}

-{{{ form }}} diff --git a/templates/matchmaking_selection_strategy.mustache b/templates/matchmaking_selection_strategy.mustache deleted file mode 100755 index e364c1b..0000000 --- a/templates/matchmaking_selection_strategy.mustache +++ /dev/null @@ -1,26 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/matchmaking_selection_strategy - - Example context (json): - { - "form": "raw html for the configuration form" - } -}} -

{{#str}} choose_selection_strategy, capquiz {{/str}}

-{{{ form }}} diff --git a/templates/question_list.mustache b/templates/question_list.mustache index cdf49a3..b2b75ee 100755 --- a/templates/question_list.mustache +++ b/templates/question_list.mustache @@ -25,7 +25,6 @@ "name": "First question", "rating": 500.0, "question_id": 56, - "rating_url": "#", "button": { "id": "id", "primary": true, @@ -39,38 +38,26 @@ } }}

{{#str}}question_list, capquiz{{/str}}

- -{{#str}} default_question_rating, capquiz {{/str}}: -
- - {{#pix}} t/check, core {{/pix}} -
- -
- {{#message}} {{{message}}} {{/message}} - - +
- - - + + {{#questions}} {{#.}} - - {{/questions}}
#{{#str}}title, capquiz{{/str}}{{#str}}rating, capquiz{{/str}}{{#str}}title, capquiz{{/str}}{{#str}}rating, capquiz{{/str}} {{#str}}action, capquiz{{/str}}
{{ index }} {{{ name }}} -
- +
+ {{#delete}} {{>core/action_link}} {{/delete}} @@ -89,7 +76,6 @@
- {{^questions}} {{#str}}no_questions_added_to_list, capquiz{{/str}} {{/questions}} diff --git a/templates/question_list_selection.mustache b/templates/question_list_selector.mustache old mode 100755 new mode 100644 similarity index 71% rename from templates/question_list_selection.mustache rename to templates/question_list_selector.mustache index 033c2fd..c543c47 --- a/templates/question_list_selection.mustache +++ b/templates/question_list_selector.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template mod_capquiz/question_list_selection + @template mod_capquiz/question_list_selector Example context (json): { @@ -27,18 +27,14 @@ "created": "2019-01-16 16:04:20", "url": "#" } - ], - "create": "raw html for a button to create the question list" + ] } }} -

- {{#str}} question_lists, capquiz {{/str}} -

-

{{#str}}select_template, capquiz{{/str}}

+

{{#str}} question_lists, capquiz {{/str}}

{{^lists}} - {{#str}}no_templates_created, capquiz{{/str}} + {{#str}} no_templates_created, capquiz {{/str}} {{/lists}} -
    +
      {{#lists}}
    1. @@ -47,13 +43,9 @@

      {{ title }}

      {{ description }}

      - {{#str}}author, capquiz{{/str}}: {{author}} -
      - {{#str}}created, capquiz{{/str}}: {{created}} +
      {{#str}}author, capquiz{{/str}}: {{ author }}
      +
      {{#str}}created, capquiz{{/str}}: {{ created }}
    2. {{/lists}}
    -
    -

    {{#str}}create_own_template, capquiz{{/str}}

    -{{{create}}} diff --git a/templates/rating_system_configuration.mustache b/templates/rating_system_configuration.mustache deleted file mode 100755 index 0e93152..0000000 --- a/templates/rating_system_configuration.mustache +++ /dev/null @@ -1,27 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/rating_system_configuration - - Example context (json): - { - "strategy": "Elo", - "form": "raw html for the form" - } -}} -

    {{#str}} configure, capquiz {{/str}} {{strategy}}

    -{{{ form }}} diff --git a/templates/rating_system_selection.mustache b/templates/rating_system_selection.mustache deleted file mode 100755 index 777b8af..0000000 --- a/templates/rating_system_selection.mustache +++ /dev/null @@ -1,26 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/rating_system_selection - - Example context (json): - { - "form": "raw html for the form" - } -}} -

    {{#str}} choose_rating_system, capquiz {{/str}}

    -{{{ form }}} diff --git a/templates/student_progress.mustache b/templates/student_progress.mustache index ee586c1..0ba13d6 100755 --- a/templates/student_progress.mustache +++ b/templates/student_progress.mustache @@ -35,39 +35,40 @@
    {{#up}}
    -
    - {{ percent }}% -
    +
    {{ percent }}%
    {{/up}} {{#down}}
    -
    - -{{ percent }}% -
    +
    -{{ percent }}%
    {{/down}}
    {{#stars}} {{#.}} - {{#pix}} star, capquiz {{/pix}} + + {{#pix}} star, capquiz {{/pix}} + {{/.}} {{/stars}} {{#blankstars}} {{#.}} - {{#pix}} blank-star, capquiz {{/pix}} + + {{#pix}} blank-star, capquiz {{/pix}} + {{/.}} {{/blankstars}} {{#nostars}} {{#.}} - {{#pix}} no-star, capquiz {{/pix}} + + {{#pix}} no-star, capquiz {{/pix}} + {{/.}} {{/nostars}} - - {{#pix}} help{{/pix}} + + {{#pix}} help {{/pix}} -
    {{/student}} diff --git a/templates/student_question_metainfo.mustache b/templates/student_question_metainfo.mustache deleted file mode 100755 index a9fca63..0000000 --- a/templates/student_question_metainfo.mustache +++ /dev/null @@ -1,83 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/student_question_metainfo - - Example context (json): - { - "metainfo": { - "rating": { - "student": 1500.0, - "question": 1450.0 - }, - "question": { - "capquiz_id": 46, - "moodle_id": 71 - } - } - } -}} -{{#metainfo}} - - - - - -
    - {{#rating}} - - - - - - - - - -
    - {{#str}}your_rating, capquiz{{/str}}: - - {{ student }} -
    - {{#str}}question_rating, capquiz{{/str}}: - - {{ question }} -
    - {{/rating}} -
    - {{#question}} - - - - - - - - - -
    - CAPQuiz ID: - - {{ capquiz_id }} -
    - Moodle ID: - - {{ moodle_id }} -
    - {{/question}} -
    -{{/metainfo}} \ No newline at end of file diff --git a/templates/unauthorized.mustache b/templates/unauthorized.mustache deleted file mode 100755 index 49a74aa..0000000 --- a/templates/unauthorized.mustache +++ /dev/null @@ -1,24 +0,0 @@ -{{! - This file is part of Moodle - http://moodle.org/ - - Moodle is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Moodle is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Moodle. If not, see . -}} -{{! - @template mod_capquiz/unauthorized - - Example context (json): - { - } -}} -

    {{#str}}need_to_log_in, capquiz{{/str}}

    \ No newline at end of file diff --git a/version.php b/version.php index 1568516..67de629 100755 --- a/version.php +++ b/version.php @@ -19,17 +19,17 @@ * * @package mod_capquiz * @author Aleksander Skrede - * @author Sebastian S. Gundersen + * @author Sebastian Gundersen * @author André Storhaug - * @copyright 2019 NTNU + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024100700; +$plugin->version = 2024102102; $plugin->requires = 2022041901; // 4.0 $plugin->cron = 0; $plugin->component = 'mod_capquiz'; $plugin->maturity = MATURITY_STABLE; -$plugin->release = '0.8.0'; +$plugin->release = '0.9.0'; diff --git a/view.php b/view.php index 6755019..308ae41 100755 --- a/view.php +++ b/view.php @@ -18,37 +18,117 @@ * Displays the correct view, depending on your role, acts as an entrypoint to the capquiz * * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU + * @author Sebastian Gundersen + * @copyright 2024 Norwegian University of Science and Technology (NTNU) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace mod_capquiz; +use mod_capquiz\api; +use mod_capquiz\capquiz; +use mod_capquiz\capquiz_question_list; +use mod_capquiz\form\view\question_list_create_form; +use mod_capquiz\output\classlist; +use mod_capquiz\output\import_page; +use mod_capquiz\output\instructor_dashboard; +use mod_capquiz\output\question_attempt_renderer; +use mod_capquiz\output\question_list; +use mod_capquiz\output\question_list_creator; +use mod_capquiz\output\question_list_selector; +use mod_capquiz\output\renderer; +use mod_capquiz\question\bank\question_bank_view; -require_once('../../config.php'); +require_once(__DIR__ . '/../../config.php'); + +global $CFG, $OUTPUT, $PAGE, $USER; + +require_once($CFG->libdir . '/formslib.php'); require_once($CFG->dirroot . '/question/editlib.php'); require_once($CFG->dirroot . '/mod/capquiz/lib.php'); -$cmid = capquiz_urls::require_course_module_id_param(); +$currentpage = optional_param('page', '', PARAM_ALPHA); +$cmid = optional_param('id', 0, PARAM_INT); +if (!$cmid) { + $cmid = required_param('cmid', PARAM_INT); +} $cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); require_login($cm->course, false, $cm); +$context = context_module::instance($cmid); + +$PAGE->set_context($context); +$PAGE->set_cm($cm); +$PAGE->set_pagelayout('incourse'); +$pageurl = new moodle_url('/mod/capquiz/view.php', ['id' => $cmid]); +if (!empty($currentpage)) { + $pageurl->param('page', $currentpage); +} +$PAGE->set_url($pageurl); + +/** @var renderer $renderer */ +$renderer = $PAGE->get_renderer('mod_capquiz'); -$capquiz = new capquiz($cmid); -$renderer = $capquiz->renderer(); +$capquizid = $cm->instance; +$capquiz = new capquiz($cm->instance); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlview); +if (has_capability('mod/capquiz:instructor', $context)) { + switch ($currentpage) { + case '': + echo $OUTPUT->header(); + echo $renderer->render(new instructor_dashboard($capquiz)); + echo $OUTPUT->footer(); + break; -if (has_capability('mod/capquiz:instructor', $capquiz->context())) { - if ($capquiz->has_question_list()) { - $renderer->display_instructor_dashboard($capquiz); - } else { - $renderer->display_choose_question_list_view(); + case 'questions': + $PAGE->set_pagelayout('admin'); + echo $OUTPUT->header(); + echo '
    '; + $qlist = capquiz_question_list::get_record(['capquiz_id' => $capquizid]); + if ($qlist) { + echo $renderer->render(new question_list($qlist)); + } + echo '
    '; + //echo '
    '; + //echo '
    '; + //echo '
    '; + //(new question_bank_view())->display(); + //echo '
    '; + //echo '
    '; + //echo '
    '; + echo $OUTPUT->footer(); + break; + + /** + $qlist->set_many([ + 'capquiz_id' => $capquizid, + 'title' => $data->title, + 'description' => $data->description, + 'author' => $USER->id, + 'is_template' => 0, + 'context_id' => context_course::instance($cm->course)->id, + ]); + */ + + case 'import': + echo $OUTPUT->header(); + echo $renderer->render(new import_page($cm->course)); + echo $OUTPUT->footer(); + break; + + case 'classlist': + echo $OUTPUT->header(); + echo $renderer->render(new classlist($capquiz)); + echo $OUTPUT->footer(); + break; + } +} else if (has_capability('mod/capquiz:student', $context)) { + $user = api::get_user($capquiz, $USER->id); + $attempt = api::get_question_attempt_for_user($capquiz, $user); + if ($attempt && !api::is_question_valid($attempt)) { + $attempt->delete(); + } + if (!$capquiz->is_grading_completed()) { + capquiz_update_grades($capquiz->to_record()); } -} else { - require_capability('mod/capquiz:student', $capquiz->context()); - // Question engine is null if the quiz is not published. - $qengine = $capquiz->question_engine($capquiz->user()); - $qengine?->delete_invalid_attempt($capquiz->user()); - $capquiz->update_grades(); - $renderer->display_question_attempt_view($capquiz); + echo $OUTPUT->header(); + echo (new question_attempt_renderer($capquiz, $renderer))->render(); + echo $OUTPUT->footer(); } diff --git a/view_create_question_list.php b/view_create_question_list.php deleted file mode 100755 index e7d9cd8..0000000 --- a/view_create_question_list.php +++ /dev/null @@ -1,47 +0,0 @@ -. - -/** - * Displays the create_question_list view - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -require_once('../../config.php'); -require_once($CFG->dirroot . '/question/editlib.php'); -require_once($CFG->dirroot . '/mod/capquiz/lib.php'); - -$cmid = capquiz_urls::require_course_module_id_param(); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); -require_capability('mod/capquiz:instructor', $context); - -$cmid = capquiz_urls::require_course_module_id_param(); -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlviewcreateqlist); - -$renderer = $capquiz->renderer(); -if ($capquiz->has_question_list()) { - $renderer->display_instructor_dashboard($capquiz); -} else { - $renderer->display_question_list_create_view($capquiz); -} diff --git a/view_grading.php b/view_grading.php deleted file mode 100644 index 8863fc6..0000000 --- a/view_grading.php +++ /dev/null @@ -1,40 +0,0 @@ -. - -/** - * Displays the grading view - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -require_once('../../config.php'); -require_once($CFG->dirroot . '/question/editlib.php'); -require_once($CFG->dirroot . '/mod/capquiz/lib.php'); - -$cmid = capquiz_urls::require_course_module_id_param(); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); -require_capability('mod/capquiz:instructor', $context); - -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlviewgrading); -$capquiz->renderer()->display_grading_configuration($capquiz); diff --git a/view_import.php b/view_import.php deleted file mode 100644 index 922cf25..0000000 --- a/view_import.php +++ /dev/null @@ -1,40 +0,0 @@ -. - -/** - * Displays the import view - * - * @package mod_capquiz - * @author Sebastian S. Gundersen - * @copyright 2019 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -require_once('../../config.php'); -require_once($CFG->dirroot . '/question/editlib.php'); -require_once($CFG->dirroot . '/mod/capquiz/lib.php'); - -$cmid = capquiz_urls::require_course_module_id_param(); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); -require_capability('mod/capquiz:instructor', $context); - -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urledit); -$capquiz->renderer()->display_import($capquiz); diff --git a/view_rating_system.php b/view_rating_system.php deleted file mode 100644 index 7d005ad..0000000 --- a/view_rating_system.php +++ /dev/null @@ -1,40 +0,0 @@ -. - -/** - * Displays the rating_system view - * - * @package mod_capquiz - * @author Aleksander Skrede - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -require_once('../../config.php'); -require_once($CFG->dirroot . '/question/editlib.php'); -require_once($CFG->dirroot . '/mod/capquiz/lib.php'); - -$cmid = capquiz_urls::require_course_module_id_param(); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); -require_capability('mod/capquiz:instructor', $context); - -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlviewratingsystemconfig); -$capquiz->renderer()->display_rating_system_configuration($capquiz); diff --git a/view_report.php b/view_report.php deleted file mode 100644 index e20aca5..0000000 --- a/view_report.php +++ /dev/null @@ -1,41 +0,0 @@ -. - -/** - * Displays capquiz report - * - * @package mod_capquiz - * @author André Storhaug - * @copyright 2019 Norwegian University of Science and Technology (NTNU) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_capquiz; - -require_once('../../config.php'); -require_once($CFG->dirroot . '/question/editlib.php'); -require_once($CFG->dirroot . '/mod/capquiz/lib.php'); - -$cmid = capquiz_urls::require_course_module_id_param(); -$cm = get_coursemodule_from_id('capquiz', $cmid, 0, false, MUST_EXIST); -require_login($cm->course, false, $cm); -$context = \context_module::instance($cmid); -require_capability('mod/capquiz:instructor', $context); - -$cmid = capquiz_urls::require_course_module_id_param(); -$capquiz = new capquiz($cmid); -capquiz_urls::set_page_url($capquiz, capquiz_urls::$urledit); -$capquiz->renderer()->display_report($capquiz);