diff --git a/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js new file mode 100644 index 00000000000..89c26ab9349 --- /dev/null +++ b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js @@ -0,0 +1,2 @@ +define ("gradingform_guide/grades/grader/gradingpanel",["exports","core/ajax","jquery"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;c=function(a){return a&&a.__esModule?a:{default:a}}(c);a.fetchCurrentGrade=function fetchCurrentGrade(a,c,d,e){return(0,b.call)([{methodname:"gradingform_guide_grader_gradingpanel_fetch",args:{component:a,contextid:c,itemname:d,gradeduserid:e}}])[0]};var d=function(a,d,e,f,g){var h=g.querySelector("form");return(0,b.call)([{methodname:"gradingform_guide_grader_gradingpanel_store",args:{component:a,contextid:d,itemname:e,gradeduserid:f,formdata:(0,c.default)(h).serialize()}}])[0]};a.storeCurrentGrade=d}); +//# sourceMappingURL=gradingpanel.min.js.map diff --git a/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js.map b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js.map new file mode 100644 index 00000000000..0d531edd152 --- /dev/null +++ b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/grades/grader/gradingpanel.js"],"names":["fetchCurrentGrade","component","contextid","itemname","gradeduserid","methodname","args","storeCurrentGrade","rootNode","form","querySelector","formdata","serialize"],"mappings":"qNA2BA,uD,oBAEiC,QAApBA,CAAAA,iBAAoB,CAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAC/E,MAAO,WAAU,CAAC,CACdC,UAAU,8CADI,CAEdC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAFQ,CAAD,CAAV,EAQH,CARG,CASV,C,CAGM,GAAMG,CAAAA,CAAiB,CAAG,SAACN,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAA+CI,CAA/C,CAA4D,CACzF,GAAMC,CAAAA,CAAI,CAAGD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CAAb,CAEA,MAAO,WAAU,CAAC,CACdL,UAAU,8CADI,CAEdC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFO,QAAQ,CAAE,cAAOF,CAAP,EAAaG,SAAb,EALR,CAFQ,CAAD,CAAV,EASH,CATG,CAUV,CAbM,C","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 * Grading panel for gradingform_guide.\n *\n * @module gradingform_guide/grades/grader/gradingpanel\n * @package gradingform_guide\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\n\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\n\nexport const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `gradingform_guide_grader_gradingpanel_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\n\nexport const storeCurrentGrade = (component, contextid, itemname, gradeduserid, rootNode) => {\n const form = rootNode.querySelector('form');\n\n return fetchMany([{\n methodname: `gradingform_guide_grader_gradingpanel_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n formdata: jQuery(form).serialize(),\n },\n }])[0];\n};\n"],"file":"gradingpanel.min.js"} \ No newline at end of file diff --git a/grade/grading/form/guide/amd/build/grades/grader/gradingpanel/comments.min.js b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel/comments.min.js new file mode 100644 index 00000000000..893922835c3 --- /dev/null +++ b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel/comments.min.js @@ -0,0 +1,2 @@ +define ("gradingform_guide/grades/grader/gradingpanel/comments",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;a.init=function init(a){var b=document.querySelector("#".concat(a));b.addEventListener("click",function(a){if(!a.target.matches("[data-gradingform_guide-role=\"frequent-comment\"]")){return}a.preventDefault();var b=a.target.closest("[data-gradingform_guide-role=\"frequent-comment\"]"),c=b.closest("[data-gradingform-guide-role=\"criterion\"]"),d=c.querySelector("[data-gradingform-guide-role=\"remark\"]");if(!d){return}if(d.value.trim()){d.value+="\n".concat(b.innerHTML)}else{d.value+=b.innerHTML}})}}); +//# sourceMappingURL=comments.min.js.map diff --git a/grade/grading/form/guide/amd/build/grades/grader/gradingpanel/comments.min.js.map b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel/comments.min.js.map new file mode 100644 index 00000000000..8d6f2a9e834 --- /dev/null +++ b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel/comments.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/comments.js"],"names":["init","rootId","rootNode","document","querySelector","addEventListener","e","target","matches","preventDefault","clicked","closest","criterion","remark","value","trim","innerHTML"],"mappings":"2KAwBoB,QAAPA,CAAAA,IAAO,CAACC,CAAD,CAAY,CAC5B,GAAMC,CAAAA,CAAQ,CAAGC,QAAQ,CAACC,aAAT,YAA2BH,CAA3B,EAAjB,CAEAC,CAAQ,CAACG,gBAAT,CAA0B,OAA1B,CAAmC,SAACC,CAAD,CAAO,CACtC,GAAI,CAACA,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiB,oDAAjB,CAAL,CAA2E,CACvE,MACH,CAEDF,CAAC,CAACG,cAAF,GALsC,GAOhCC,CAAAA,CAAO,CAAGJ,CAAC,CAACC,MAAF,CAASI,OAAT,CAAiB,oDAAjB,CAPsB,CAQhCC,CAAS,CAAGF,CAAO,CAACC,OAAR,CAAgB,6CAAhB,CARoB,CAShCE,CAAM,CAAGD,CAAS,CAACR,aAAV,CAAwB,0CAAxB,CATuB,CAWtC,GAAI,CAACS,CAAL,CAAa,CACT,MACH,CAED,GAAIA,CAAM,CAACC,KAAP,CAAaC,IAAb,EAAJ,CAAyB,CACrBF,CAAM,CAACC,KAAP,cAAqBJ,CAAO,CAACM,SAA7B,CACH,CAFD,IAEO,CACHH,CAAM,CAACC,KAAP,EAAgBJ,CAAO,CAACM,SAC3B,CACJ,CApBD,CAqBH,C","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 * Grading panel frequently used comments selector.\n *\n * @module gradingform_guide/grades/grader/gradingpanel/comments\n * @package gradingform_guide\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const init = (rootId) => {\n const rootNode = document.querySelector(`#${rootId}`);\n\n rootNode.addEventListener('click', (e) => {\n if (!e.target.matches('[data-gradingform_guide-role=\"frequent-comment\"]')) {\n return;\n }\n\n e.preventDefault();\n\n const clicked = e.target.closest('[data-gradingform_guide-role=\"frequent-comment\"]');\n const criterion = clicked.closest('[data-gradingform-guide-role=\"criterion\"]');\n const remark = criterion.querySelector('[data-gradingform-guide-role=\"remark\"]');\n\n if (!remark) {\n return;\n }\n\n if (remark.value.trim()) {\n remark.value += `\\n${clicked.innerHTML}`;\n } else {\n remark.value += clicked.innerHTML;\n }\n });\n};\n"],"file":"comments.min.js"} \ No newline at end of file diff --git a/grade/grading/form/guide/amd/src/grades/grader/gradingpanel.js b/grade/grading/form/guide/amd/src/grades/grader/gradingpanel.js new file mode 100644 index 00000000000..a0324c4e6ac --- /dev/null +++ b/grade/grading/form/guide/amd/src/grades/grader/gradingpanel.js @@ -0,0 +1,56 @@ +// 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 . + +/** + * Grading panel for gradingform_guide. + * + * @module gradingform_guide/grades/grader/gradingpanel + * @package gradingform_guide + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {call as fetchMany} from 'core/ajax'; + +// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send() +import jQuery from 'jquery'; + +export const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => { + return fetchMany([{ + methodname: `gradingform_guide_grader_gradingpanel_fetch`, + args: { + component, + contextid, + itemname, + gradeduserid, + }, + }])[0]; +}; + + +export const storeCurrentGrade = (component, contextid, itemname, gradeduserid, rootNode) => { + const form = rootNode.querySelector('form'); + + return fetchMany([{ + methodname: `gradingform_guide_grader_gradingpanel_store`, + args: { + component, + contextid, + itemname, + gradeduserid, + formdata: jQuery(form).serialize(), + }, + }])[0]; +}; diff --git a/grade/grading/form/guide/amd/src/grades/grader/gradingpanel/comments.js b/grade/grading/form/guide/amd/src/grades/grader/gradingpanel/comments.js new file mode 100644 index 00000000000..8e69154f0fb --- /dev/null +++ b/grade/grading/form/guide/amd/src/grades/grader/gradingpanel/comments.js @@ -0,0 +1,49 @@ +// 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 . + +/** + * Grading panel frequently used comments selector. + * + * @module gradingform_guide/grades/grader/gradingpanel/comments + * @package gradingform_guide + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export const init = (rootId) => { + const rootNode = document.querySelector(`#${rootId}`); + + rootNode.addEventListener('click', (e) => { + if (!e.target.matches('[data-gradingform_guide-role="frequent-comment"]')) { + return; + } + + e.preventDefault(); + + const clicked = e.target.closest('[data-gradingform_guide-role="frequent-comment"]'); + const criterion = clicked.closest('[data-gradingform-guide-role="criterion"]'); + const remark = criterion.querySelector('[data-gradingform-guide-role="remark"]'); + + if (!remark) { + return; + } + + if (remark.value.trim()) { + remark.value += `\n${clicked.innerHTML}`; + } else { + remark.value += clicked.innerHTML; + } + }); +}; diff --git a/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/fetch.php b/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/fetch.php new file mode 100644 index 00000000000..3f9c8108f92 --- /dev/null +++ b/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/fetch.php @@ -0,0 +1,283 @@ +. + +/** + * Web services relating to fetching of a marking guide for the grading panel. + * + * @package gradingform_guide + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types = 1); + +namespace gradingform_guide\grades\grader\gradingpanel\external; + +use coding_exception; +use context; +use core_user; +use core_grades\component_gradeitem as gradeitem; +use core_grades\component_gradeitems; +use external_api; +use external_format_value; +use external_function_parameters; +use external_multiple_structure; +use external_single_structure; +use external_value; +use external_warnings; +use moodle_exception; +use stdClass; + +/** + * Web services relating to fetching of a marking guide for the grading panel. + * + * @package gradingform_guide + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class fetch extends external_api { + + /** + * Describes the parameters for fetching the grading panel for a simple grade. + * + * @return external_function_parameters + * @since Moodle 3.8 + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters ([ + 'component' => new external_value( + PARAM_ALPHANUMEXT, + 'The name of the component', + VALUE_REQUIRED + ), + 'contextid' => new external_value( + PARAM_INT, + 'The ID of the context being graded', + VALUE_REQUIRED + ), + 'itemname' => new external_value( + PARAM_ALPHANUM, + 'The grade item itemname being graded', + VALUE_REQUIRED + ), + 'gradeduserid' => new external_value( + PARAM_INT, + 'The ID of the user show', + VALUE_REQUIRED + ), + ]); + } + + /** + * Fetch the data required to build a grading panel for a simple grade. + * + * @param string $component + * @param int $contextid + * @param string $itemname + * @param int $gradeduserid + * @return array + * @since Moodle 3.8 + */ + public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array { + global $USER; + + [ + 'component' => $component, + 'contextid' => $contextid, + 'itemname' => $itemname, + 'gradeduserid' => $gradeduserid, + ] = self::validate_parameters(self::execute_parameters(), [ + 'component' => $component, + 'contextid' => $contextid, + 'itemname' => $itemname, + 'gradeduserid' => $gradeduserid, + ]); + + // Validate the context. + $context = context::instance_by_id($contextid); + self::validate_context($context); + + // Validate that the supplied itemname is a gradable item. + if (!component_gradeitems::is_valid_itemname($component, $itemname)) { + throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); + } + + // Fetch the gradeitem instance. + $gradeitem = gradeitem::instance($component, $context, $itemname); + + if ('guide' !== $gradeitem->get_advanced_grading_method()) { + throw new moodle_exception( + "The {$itemname} item in {$component}/{$contextid} is not configured for advanced grading with a marking guide" + ); + } + + // Fetch the actual data. + $gradeduser = core_user::get_user($gradeduserid); + + return self::get_fetch_data($gradeitem, $gradeduser); + } + + /** + * Get the data to be fetched. + * + * @param component_gradeitem $gradeitem + * @return array + */ + public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser): array { + global $USER; + + $grade = $gradeitem->get_grade_for_user($gradeduser, $USER); + $instance = $gradeitem->get_advanced_grading_instance($USER, $grade); + $controller = $instance->get_controller(); + $definition = $controller->get_definition(); + $fillings = $instance->get_guide_filling(); + $context = $controller->get_context(); + $definitionid = (int) $definition->id; + + $criterion = []; + if ($definition->guide_criteria) { + $criterion = array_map(function($criterion) use ($definitionid, $fillings, $context) { + $result = [ + 'id' => $criterion['id'], + 'name' => $criterion['shortname'], + 'maxscore' => $criterion['maxscore'], + 'description' => self::get_formatted_text( + $context, + $definitionid, + 'description', + $criterion['description'], + (int) $criterion['descriptionformat'] + ), + 'descriptionmarkers' => self::get_formatted_text( + $context, + $definitionid, + 'descriptionmarkers', + $criterion['descriptionmarkers'], + (int) $criterion['descriptionmarkersformat'] + ), + 'score' => null, + 'remark' => null, + ]; + + if (array_key_exists($criterion['id'], $fillings['criteria'])) { + $filling = $fillings['criteria'][$criterion['id']]; + + $result['score'] = $filling['score']; + $result['remark'] = self::get_formatted_text( + $context, + $definitionid, + 'remark', + $filling['remark'], + (int) $filling['remarkformat'] + ); + } + + return $result; + }, $definition->guide_criteria); + } + + $comments = []; + if ($definition->guide_comments) { + $comments = array_map(function($comment) use ($definitionid, $context) { + return [ + 'id' => $comment['id'], + 'sortorder' => $comment['sortorder'], + 'description' => self::get_formatted_text( + $context, + $definitionid, + 'description', + $comment['description'], + (int) $comment['descriptionformat'] + ), + ]; + }, $definition->guide_comments); + } + + return [ + 'templatename' => 'gradingform_guide/grades/grader/gradingpanel', + 'grade' => [ + 'instanceid' => $instance->get_id(), + 'criterion' => $criterion, + 'hascomments' => !empty($comments), + 'comments' => $comments, + 'timecreated' => $grade->timecreated, + 'timemodified' => $grade->timemodified, + ], + 'warnings' => [], + ]; + } + + /** + * Describes the data returned from the external function. + * + * @return external_single_structure + * @since Moodle 3.8 + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'templatename' => new external_value(PARAM_SAFEPATH, 'The template to use when rendering this data'), + 'grade' => new external_single_structure([ + 'instanceid' => new external_value(PARAM_INT, 'The id of the current grading instance'), + 'criterion' => new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'The id of the criterion'), + 'name' => new external_value(PARAM_RAW, 'The name of the criterion'), + 'maxscore' => new external_value(PARAM_FLOAT, 'The maximum score for this criterion'), + 'description' => new external_value(PARAM_RAW, 'The description of the criterion'), + 'descriptionmarkers' => new external_value(PARAM_RAW, 'The description of the criterion for markers'), + 'score' => new external_value(PARAM_FLOAT, 'The current score for user being assessed', VALUE_OPTIONAL), + 'remark' => new external_value(PARAM_RAW, 'Any remarks for this criterion for the user being assessed', VALUE_OPTIONAL), + ]), + 'The criterion by which this item will be graded' + ), + 'hascomments' => new external_value(PARAM_BOOL, 'Whether there are any frequently-used comments'), + 'comments' => new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'Comment id'), + 'sortorder' => new external_value(PARAM_INT, 'The sortorder of this comment'), + 'description' => new external_value(PARAM_RAW, 'The comment value'), + ]), + 'Frequently used comments' + ), + 'timecreated' => new external_value(PARAM_INT, 'The time that the grade was created'), + 'timemodified' => new external_value(PARAM_INT, 'The time that the grade was last updated'), + ]), + 'warnings' => new external_warnings(), + ]); + } + + /** + * Get a formatted version of the remark/description/etc. + * + * @param context $context + * @param int $definitionid + * @param string $filearea The file area of the field + * @param string $text The text to be formatted + * @param int $format The input format of the string + * @return string + */ + protected static function get_formatted_text(context $context, int $definitionid, string $filearea, string $text, int $format): string { + $formatoptions = [ + 'noclean' => false, + 'trusted' => false, + 'filter' => true, + ]; + + [$newtext, ] = external_format_text($text, $format, $context, 'grading', $filearea, $definitionid, $formatoptions); + + return $newtext; + } +} diff --git a/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/store.php b/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/store.php new file mode 100644 index 00000000000..c9fdaae91a2 --- /dev/null +++ b/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/store.php @@ -0,0 +1,160 @@ +. + +/** + * Web services relating to fetching of a marking guide for the grading panel. + * + * @package gradingform_guide + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +declare(strict_types = 1); + +namespace gradingform_guide\grades\grader\gradingpanel\external; + +use coding_exception; +use context; +use core_grades\component_gradeitem as gradeitem; +use core_grades\component_gradeitems; +use core_user; +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use moodle_exception; + +/** + * Web services relating to storing of a marking guide for the grading panel. + * + * @package gradingform_guide + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class store extends external_api { + + /** + * Describes the parameters for storing the grading panel for a simple grade. + * + * @return external_function_parameters + * @since Moodle 3.8 + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters ([ + 'component' => new external_value( + PARAM_ALPHANUMEXT, + 'The name of the component', + VALUE_REQUIRED + ), + 'contextid' => new external_value( + PARAM_INT, + 'The ID of the context being graded', + VALUE_REQUIRED + ), + 'itemname' => new external_value( + PARAM_ALPHANUM, + 'The grade item itemname being graded', + VALUE_REQUIRED + ), + 'gradeduserid' => new external_value( + PARAM_INT, + 'The ID of the user show', + VALUE_REQUIRED + ), + 'formdata' => new external_value( + PARAM_RAW, + 'The serialised form data representing the grade', + VALUE_REQUIRED + ), + ]); + } + + /** + * Fetch the data required to build a grading panel for a simple grade. + * + * @param string $component + * @param int $contextid + * @param string $itemname + * @param int $gradeduserid + * @return array + * @since Moodle 3.8 + */ + public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, string $formdata): array { + global $USER; + + [ + 'component' => $component, + 'contextid' => $contextid, + 'itemname' => $itemname, + 'gradeduserid' => $gradeduserid, + 'formdata' => $formdata, + ] = self::validate_parameters(self::execute_parameters(), [ + 'component' => $component, + 'contextid' => $contextid, + 'itemname' => $itemname, + 'gradeduserid' => $gradeduserid, + 'formdata' => $formdata, + ]); + + // Validate the context. + $context = context::instance_by_id($contextid); + self::validate_context($context); + + // Validate that the supplied itemname is a gradable item. + if (!component_gradeitems::is_valid_itemname($component, $itemname)) { + throw new coding_exception("The '{$itemname}' item is not valid for the '{$component}' component"); + } + + // Fetch the gradeitem instance. + $gradeitem = gradeitem::instance($component, $context, $itemname); + + // Validate that this gradeitem is actually enabled. + if (!$gradeitem->is_grading_enabled()) { + throw new moodle_exception("Grading is not enabled for {$itemname} in this context"); + } + + // Fetch the record for the graded user. + $gradeduser = core_user::get_user($gradeduserid); + + // Require that this user can save grades. + $gradeitem->require_user_can_grade($gradeduser, $USER); + + if ('guide' !== $gradeitem->get_advanced_grading_method()) { + throw new moodle_exception( + "The {$itemname} item in {$component}/{$contextid} is not configured for advanced grading with a marking guide" + ); + } + + // Parse the serialised string into an object. + $data = []; + parse_str($formdata, $data); + + // Grade. + $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); + + return fetch::get_fetch_data($gradeitem, $gradeduser); + } + + /** + * Describes the data returned from the external function. + * + * @return external_single_structure + * @since Moodle 3.8 + */ + public static function execute_returns(): external_single_structure { + return fetch::execute_returns(); + } +} diff --git a/grade/grading/form/guide/db/services.php b/grade/grading/form/guide/db/services.php new file mode 100644 index 00000000000..24a947ed81a --- /dev/null +++ b/grade/grading/form/guide/db/services.php @@ -0,0 +1,42 @@ +. + +/** + * External functions and service definitions for the Marking Guide advanced grading form. + * + * @package mod_forum + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$functions = [ + 'gradingform_guide_grader_gradingpanel_fetch' => [ + 'classname' => 'gradingform_guide\\grades\\grader\\gradingpanel\\external\\fetch', + 'methodname' => 'execute', + 'description' => 'Fetch the data required to display the grader grading panel, ' . + 'creating the grade item if required', + 'type' => 'write', + 'ajax' => true, + ], + 'gradingform_guide_grader_gradingpanel_store' => [ + 'classname' => 'gradingform_guide\\grades\\grader\\gradingpanel\\external\\store', + 'methodname' => 'execute', + 'description' => 'Store the grading data for a user from the grader grading panel.', + 'type' => 'write', + 'ajax' => true, + ], +]; diff --git a/grade/grading/form/guide/lang/en/gradingform_guide.php b/grade/grading/form/guide/lang/en/gradingform_guide.php index 9343b4aabdf..6c9aebd2d6e 100644 --- a/grade/grading/form/guide/lang/en/gradingform_guide.php +++ b/grade/grading/form/guide/lang/en/gradingform_guide.php @@ -25,6 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $string['addcomment'] = 'Add frequently used comment'; +$string['additionalcomments'] = 'Additional comments'; $string['addcriterion'] = 'Add criterion'; $string['alwaysshowdefinition'] = 'Show guide definition to students'; $string['backtoediting'] = 'Back to editing'; @@ -73,6 +74,7 @@ $string['insertcomment'] = 'Insert frequently used comment'; $string['maxscore'] = 'Maximum score'; $string['name'] = 'Name'; $string['needregrademessage'] = 'The marking guide definition was changed after this student had been graded. The student can not see this marking guide until you check the marking guide and update the grade.'; +$string['outof'] = 'Out of {$a}'; $string['pluginname'] = 'Marking guide'; $string['previewmarkingguide'] = 'Preview marking guide'; $string['privacy:metadata:criterionid'] = 'An identifier to a criterion for advanced marking.'; diff --git a/grade/grading/form/guide/lib.php b/grade/grading/form/guide/lib.php index 4a4e002e994..ffec06cbdec 100644 --- a/grade/grading/form/guide/lib.php +++ b/grade/grading/form/guide/lib.php @@ -997,3 +997,15 @@ class gradingform_guide_instance extends gradingform_instance { return $html; } } + +/** + * Get the icon mapping for font-awesome. + * + * @return array + */ +function gradingform_guide_get_fontawesome_icon_map(): array { + return [ + 'gradingform_guide:info' => 'fa-info-circle', + 'gradingform_guide:plus' => 'fa-plus', + ]; +} diff --git a/grade/grading/form/guide/pix/info.png b/grade/grading/form/guide/pix/info.png new file mode 100644 index 00000000000..6f9aa778b78 Binary files /dev/null and b/grade/grading/form/guide/pix/info.png differ diff --git a/grade/grading/form/guide/pix/info.svg b/grade/grading/form/guide/pix/info.svg new file mode 100644 index 00000000000..f14d54902ca --- /dev/null +++ b/grade/grading/form/guide/pix/info.svg @@ -0,0 +1,3 @@ + +]> \ No newline at end of file diff --git a/grade/grading/form/guide/pix/plus.png b/grade/grading/form/guide/pix/plus.png new file mode 100644 index 00000000000..988d917581e Binary files /dev/null and b/grade/grading/form/guide/pix/plus.png differ diff --git a/grade/grading/form/guide/pix/plus.svg b/grade/grading/form/guide/pix/plus.svg new file mode 100644 index 00000000000..63ccf861cbb --- /dev/null +++ b/grade/grading/form/guide/pix/plus.svg @@ -0,0 +1,3 @@ + +]> \ No newline at end of file diff --git a/grade/grading/form/guide/styles.css b/grade/grading/form/guide/styles.css index e3cd9dcb903..08cbb9d3a61 100644 --- a/grade/grading/form/guide/styles.css +++ b/grade/grading/form/guide/styles.css @@ -241,3 +241,8 @@ max-height: 80vh; overflow-y: auto; } +.gradingform_guide-frequent-comments { + position: absolute; + top: 7px; + right: 0px; +} diff --git a/grade/grading/form/guide/styles.scss b/grade/grading/form/guide/styles.scss new file mode 100644 index 00000000000..4898971a0cf --- /dev/null +++ b/grade/grading/form/guide/styles.scss @@ -0,0 +1,5 @@ +.gradingform_guide-fac { + position: absolute; + right: -5px; + top: 5px; +} diff --git a/grade/grading/form/guide/templates/grades/grader/gradingpanel.mustache b/grade/grading/form/guide/templates/grades/grader/gradingpanel.mustache new file mode 100644 index 00000000000..258832829b9 --- /dev/null +++ b/grade/grading/form/guide/templates/grades/grader/gradingpanel.mustache @@ -0,0 +1,74 @@ +
+ + {{#criterion}} +
+
+ {{name}} + +
+
+
+ {{{description}}} + {{#descriptionmarkers}} +
+ {{{descriptionmarkers}}} + {{/descriptionmarkers}} +
+
+
+ + + {{#str}}grade_help, gradingform_guide{{/str}} +
+
+ +
+ + {{#hascomments}} + + {{/hascomments}} +
+ {{#hascomments}} +
+
+
+ {{#comments}} + + {{/comments}} +
+
+
+ {{/hascomments}} + {{#str}}grade_help, gradingform_guide{{/str}} +
+
+ {{/criterion}} +
+{{#js}} +require(['gradingform_guide/grades/grader/gradingpanel/comments'], function(Comments) { + Comments.init('gradingform_guide-{{uniqid}}'); +}); +{{/js}} diff --git a/grade/grading/form/guide/tests/generator/lib.php b/grade/grading/form/guide/tests/generator/lib.php index faf0cb14f07..ffb7634a7fb 100644 --- a/grade/grading/form/guide/tests/generator/lib.php +++ b/grade/grading/form/guide/tests/generator/lib.php @@ -173,10 +173,14 @@ class gradingform_guide_generator extends component_generator_base { * @param context_module $context * @return gradingform_guide_controller */ - public function get_test_guide(context_module $context): gradingform_guide_controller { + public function get_test_guide( + context_module $context, + string $component = 'mod_assign', + string $areaname = 'submission' + ): gradingform_guide_controller { $generator = \testing_util::get_data_generator(); $gradinggenerator = $generator->get_plugin_generator('core_grading'); - $controller = $gradinggenerator->create_instance($context, 'mod_assign', 'submission', 'guide'); + $controller = $gradinggenerator->create_instance($context, $component, $areaname, 'guide'); $generator = \testing_util::get_data_generator(); $guidegenerator = $generator->get_plugin_generator('gradingform_guide'); diff --git a/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_fetch_test.php b/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_fetch_test.php new file mode 100644 index 00000000000..1f58bbddb02 --- /dev/null +++ b/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_fetch_test.php @@ -0,0 +1,296 @@ +. + +/** + * Unit tests for core_grades\component_gradeitems; + * + * @package core_grades + * @category test + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ + +declare(strict_types = 1); + +namespace gradingform_guide\grades\grader\gradingpanel\external; + +use advanced_testcase; +use coding_exception; +use core_grades\component_gradeitem; +use external_api; +use mod_forum\local\entities\forum as forum_entity; +use moodle_exception; + +/** + * Unit tests for core_grades\component_gradeitems; + * + * @package core_grades + * @category test + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class fetch_test extends advanced_testcase { + + public static function setupBeforeClass(): void { + global $CFG; + require_once("{$CFG->libdir}/externallib.php"); + } + + /** + * Ensure that an execute with an invalid component is rejected. + */ + public function test_execute_invalid_component(): void { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $this->expectException(coding_exception::class); + $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); + fetch::execute('mod_invalid', 1, 'foo', 2); + } + + /** + * Ensure that an execute with an invalid itemname on a valid component is rejected. + */ + public function test_execute_invalid_itemname(): void { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $this->expectException(coding_exception::class); + $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); + fetch::execute('mod_forum', 1, 'foo', 2); + } + + /** + * Ensure that an execute against a different grading method is rejected. + */ + public function test_execute_incorrect_type(): void { + $this->resetAfterTest(); + + $forum = $this->get_forum_instance([ + // Negative numbers mean a scale. + 'grade_forum' => 5, + ]); + $course = $forum->get_course_record(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $this->setUser($teacher); + + $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); + + $this->expectException(moodle_exception::class); + $this->expectExceptionMessage("not configured for advanced grading with a marking guide"); + fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id); + } + + /** + * Ensure that an execute against the correct grading method returns the current state of the user. + */ + public function test_execute_fetch_empty(): void { + $this->resetAfterTest(); + + [ + 'forum' => $forum, + 'controller' => $controller, + 'definition' => $definition, + 'student' => $student, + 'teacher' => $teacher, + ] = $this->get_test_data(); + + $this->setUser($teacher); + + $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); + + $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id); + $result = external_api::clean_returnvalue(fetch::execute_returns(), $result); + + $this->assertIsArray($result); + $this->assertArrayHasKey('templatename', $result); + + $this->assertEquals('gradingform_guide/grades/grader/gradingpanel', $result['templatename']); + + $this->assertArrayHasKey('grade', $result); + $this->assertIsArray($result['grade']); + + $this->assertIsInt($result['grade']['timecreated']); + $this->assertArrayHasKey('timemodified', $result['grade']); + $this->assertIsInt($result['grade']['timemodified']); + + $this->assertArrayHasKey('warnings', $result); + $this->assertIsArray($result['warnings']); + $this->assertEmpty($result['warnings']); + + $this->assertArrayHasKey('criterion', $result['grade']); + $criteria = $result['grade']['criterion']; + $this->assertCount(count($definition->guide_criteria), $criteria); + foreach ($criteria as $criterion) { + $this->assertArrayHasKey('id', $criterion); + $criterionid = $criterion['id']; + $sourcecriterion = $definition->guide_criteria[$criterionid]; + + $this->assertArrayHasKey('name', $criterion); + $this->assertEquals($sourcecriterion['shortname'], $criterion['name']); + + $this->assertArrayHasKey('maxscore', $criterion); + $this->assertEquals($sourcecriterion['maxscore'], $criterion['maxscore']); + + $this->assertArrayHasKey('description', $criterion); + $this->assertEquals($sourcecriterion['description'], $criterion['description']); + + $this->assertArrayHasKey('descriptionmarkers', $criterion); + $this->assertEquals($sourcecriterion['descriptionmarkers'], $criterion['descriptionmarkers']); + + $this->assertArrayHasKey('score', $criterion); + $this->assertEmpty($criterion['score']); + + $this->assertArrayHasKey('remark', $criterion); + $this->assertEmpty($criterion['remark']); + } + } + + /** + * Ensure that an execute against the correct grading method returns the current state of the user. + */ + public function test_execute_fetch_graded(): void { + $this->resetAfterTest(); + $generator = \testing_util::get_data_generator(); + $guidegenerator = $generator->get_plugin_generator('gradingform_guide'); + + [ + 'forum' => $forum, + 'controller' => $controller, + 'definition' => $definition, + 'student' => $student, + 'teacher' => $teacher, + ] = $this->get_test_data(); + + $this->setUser($teacher); + + $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); + $grade = $gradeitem->get_grade_for_user($student, $teacher); + $instance = $gradeitem->get_advanced_grading_instance($teacher, $grade); + + $submissiondata = $guidegenerator->get_test_form_data($controller, (int) $student->id, + 10, 'Propper good speling', + 0, 'ASCII art is not a picture' + ); + + $gradeitem->store_grade_from_formdata($student, $teacher, (object) [ + 'instanceid' => $instance->get_id(), + 'advancedgrading' => $submissiondata, + ]); + + $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id); + $result = external_api::clean_returnvalue(fetch::execute_returns(), $result); + + $this->assertIsArray($result); + $this->assertArrayHasKey('templatename', $result); + + $this->assertEquals('gradingform_guide/grades/grader/gradingpanel', $result['templatename']); + + $this->assertArrayHasKey('grade', $result); + $this->assertIsArray($result['grade']); + + $this->assertIsInt($result['grade']['timecreated']); + $this->assertArrayHasKey('timemodified', $result['grade']); + $this->assertIsInt($result['grade']['timemodified']); + + $this->assertArrayHasKey('warnings', $result); + $this->assertIsArray($result['warnings']); + $this->assertEmpty($result['warnings']); + + $this->assertArrayHasKey('criterion', $result['grade']); + $criteria = $result['grade']['criterion']; + $this->assertCount(count($definition->guide_criteria), $criteria); + foreach ($criteria as $criterion) { + $this->assertArrayHasKey('id', $criterion); + $criterionid = $criterion['id']; + $sourcecriterion = $definition->guide_criteria[$criterionid]; + + $this->assertArrayHasKey('name', $criterion); + $this->assertEquals($sourcecriterion['shortname'], $criterion['name']); + + $this->assertArrayHasKey('maxscore', $criterion); + $this->assertEquals($sourcecriterion['maxscore'], $criterion['maxscore']); + + $this->assertArrayHasKey('description', $criterion); + $this->assertEquals($sourcecriterion['description'], $criterion['description']); + + $this->assertArrayHasKey('descriptionmarkers', $criterion); + $this->assertEquals($sourcecriterion['descriptionmarkers'], $criterion['descriptionmarkers']); + + $this->assertArrayHasKey('score', $criterion); + $this->assertArrayHasKey('remark', $criterion); + } + + $this->assertEquals(10, $criteria[0]['score']); + $this->assertEquals('Propper good speling', $criteria[0]['remark']); + $this->assertEquals(0, $criteria[1]['score']); + $this->assertEquals('ASCII art is not a picture', $criteria[1]['remark']); + } + + /** + * Get a forum instance. + * + * @param array $config + * @return forum_entity + */ + protected function get_forum_instance(array $config = []): forum_entity { + $this->resetAfterTest(); + + $datagenerator = $this->getDataGenerator(); + $course = $datagenerator->create_course(); + $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id])); + + $vaultfactory = \mod_forum\local\container::get_vault_factory(); + $vault = $vaultfactory->get_forum_vault(); + + return $vault->get_from_id((int) $forum->id); + } + + /** + * Get test data for forums graded using a marking guide. + * + * @return array + */ + protected function get_test_data(): array { + global $DB; + + $this->resetAfterTest(); + + $generator = \testing_util::get_data_generator(); + $guidegenerator = $generator->get_plugin_generator('gradingform_guide'); + + $forum = $this->get_forum_instance(); + $course = $forum->get_course_record(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + $this->setUser($teacher); + $controller = $guidegenerator->get_test_guide($forum->get_context(), 'forum', 'forum'); + $definition = $controller->get_definition(); + + $DB->set_field('forum', 'grade_forum', count($definition->guide_criteria), ['id' => $forum->get_id()]); + return [ + 'forum' => $forum, + 'controller' => $controller, + 'definition' => $definition, + 'student' => $student, + 'teacher' => $teacher, + ]; + } +} diff --git a/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_store_test.php b/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_store_test.php new file mode 100644 index 00000000000..bc4635091e1 --- /dev/null +++ b/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_store_test.php @@ -0,0 +1,249 @@ +. + +/** + * Unit tests for core_grades\component_gradeitems; + * + * @package core_grades + * @category test + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ + +declare(strict_types = 1); + +namespace gradingform_guide\grades\grader\gradingpanel\external; + +use advanced_testcase; +use coding_exception; +use core_grades\component_gradeitem; +use external_api; +use mod_forum\local\entities\forum as forum_entity; +use moodle_exception; + +/** + * Unit tests for core_grades\component_gradeitems; + * + * @package core_grades + * @category test + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class store_test extends advanced_testcase { + + public static function setupBeforeClass(): void { + global $CFG; + require_once("{$CFG->libdir}/externallib.php"); + } + + /** + * Ensure that an execute with an invalid component is rejected. + */ + public function test_execute_invalid_component(): void { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $this->expectException(coding_exception::class); + $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); + store::execute('mod_invalid', 1, 'foo', 2, 'formdata'); + } + + /** + * Ensure that an execute with an invalid itemname on a valid component is rejected. + */ + public function test_execute_invalid_itemname(): void { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $this->expectException(coding_exception::class); + $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); + store::execute('mod_forum', 1, 'foo', 2, 'formdata'); + } + + /** + * Ensure that an execute against a different grading method is rejected. + */ + public function test_execute_incorrect_type(): void { + $this->resetAfterTest(); + + $forum = $this->get_forum_instance([ + 'grade_forum' => 5, + ]); + $course = $forum->get_course_record(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $this->setUser($teacher); + + $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); + + $this->expectException(moodle_exception::class); + $this->expectExceptionMessage("not configured for advanced grading with a marking guide"); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + } + + /** + * Ensure that an execute against a different grading method is rejected. + */ + public function test_execute_disabled(): void { + $this->resetAfterTest(); + + $forum = $this->get_forum_instance(); + $course = $forum->get_course_record(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $this->setUser($teacher); + + $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); + + $this->expectException(moodle_exception::class); + $this->expectExceptionMessage("Grading is not enabled"); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + } + + /** + * Ensure that an execute against the correct grading method returns the current state of the user. + */ + public function test_execute_store_graded(): void { + $this->resetAfterTest(); + $generator = \testing_util::get_data_generator(); + $guidegenerator = $generator->get_plugin_generator('gradingform_guide'); + + [ + 'forum' => $forum, + 'controller' => $controller, + 'definition' => $definition, + 'student' => $student, + 'teacher' => $teacher, + ] = $this->get_test_data(); + + $this->setUser($teacher); + + $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); + $grade = $gradeitem->get_grade_for_user($student, $teacher); + $instance = $gradeitem->get_advanced_grading_instance($teacher, $grade); + + $submissiondata = $guidegenerator->get_test_form_data($controller, (int) $student->id, + 10, 'Propper good speling', + 0, 'ASCII art is not a picture' + ); + + $formdata = http_build_query((object) [ + 'instanceid' => $instance->get_id(), + 'advancedgrading' => $submissiondata, + ], '', '&'); + + $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, $formdata); + $result = external_api::clean_returnvalue(store::execute_returns(), $result); + + $this->assertIsArray($result); + $this->assertArrayHasKey('templatename', $result); + + $this->assertEquals('gradingform_guide/grades/grader/gradingpanel', $result['templatename']); + + $this->assertArrayHasKey('grade', $result); + $this->assertIsArray($result['grade']); + + $this->assertIsInt($result['grade']['timecreated']); + $this->assertArrayHasKey('timemodified', $result['grade']); + $this->assertIsInt($result['grade']['timemodified']); + + $this->assertArrayHasKey('warnings', $result); + $this->assertIsArray($result['warnings']); + $this->assertEmpty($result['warnings']); + + $this->assertArrayHasKey('criterion', $result['grade']); + $criteria = $result['grade']['criterion']; + $this->assertCount(count($definition->guide_criteria), $criteria); + foreach ($criteria as $criterion) { + $this->assertArrayHasKey('id', $criterion); + $criterionid = $criterion['id']; + $sourcecriterion = $definition->guide_criteria[$criterionid]; + + $this->assertArrayHasKey('name', $criterion); + $this->assertEquals($sourcecriterion['shortname'], $criterion['name']); + + $this->assertArrayHasKey('maxscore', $criterion); + $this->assertEquals($sourcecriterion['maxscore'], $criterion['maxscore']); + + $this->assertArrayHasKey('description', $criterion); + $this->assertEquals($sourcecriterion['description'], $criterion['description']); + + $this->assertArrayHasKey('descriptionmarkers', $criterion); + $this->assertEquals($sourcecriterion['descriptionmarkers'], $criterion['descriptionmarkers']); + + $this->assertArrayHasKey('score', $criterion); + $this->assertArrayHasKey('remark', $criterion); + } + + $this->assertEquals(10, $criteria[0]['score']); + $this->assertEquals('Propper good speling', $criteria[0]['remark']); + $this->assertEquals(0, $criteria[1]['score']); + $this->assertEquals('ASCII art is not a picture', $criteria[1]['remark']); + } + + /** + * Get a forum instance. + * + * @param array $config + * @return forum_entity + */ + protected function get_forum_instance(array $config = []): forum_entity { + $this->resetAfterTest(); + + $datagenerator = $this->getDataGenerator(); + $course = $datagenerator->create_course(); + $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id])); + + $vaultfactory = \mod_forum\local\container::get_vault_factory(); + $vault = $vaultfactory->get_forum_vault(); + + return $vault->get_from_id((int) $forum->id); + } + + /** + * Get test data for forums graded using a marking guide. + * + * @return array + */ + protected function get_test_data(): array { + global $DB; + + $this->resetAfterTest(); + + $generator = \testing_util::get_data_generator(); + $guidegenerator = $generator->get_plugin_generator('gradingform_guide'); + + $forum = $this->get_forum_instance(); + $course = $forum->get_course_record(); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); + + $this->setUser($teacher); + $controller = $guidegenerator->get_test_guide($forum->get_context(), 'forum', 'forum'); + $definition = $controller->get_definition(); + + $DB->set_field('forum', 'grade_forum', count($definition->guide_criteria), ['id' => $forum->get_id()]); + return [ + 'forum' => $forum, + 'controller' => $controller, + 'definition' => $definition, + 'student' => $student, + 'teacher' => $teacher, + ]; + } +} diff --git a/grade/grading/form/guide/version.php b/grade/grading/form/guide/version.php index 9b6c8270a0f..a774d44e9b4 100644 --- a/grade/grading/form/guide/version.php +++ b/grade/grading/form/guide/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'gradingform_guide'; -$plugin->version = 2019052000; +$plugin->version = 2019100300; $plugin->requires = 2019051100; -$plugin->maturity = MATURITY_STABLE; \ No newline at end of file +$plugin->maturity = MATURITY_STABLE;