diff --git a/admin/tool/behat/tests/behat/data_generators.feature b/admin/tool/behat/tests/behat/data_generators.feature index fb8933f4281..19afbe449b4 100644 --- a/admin/tool/behat/tests/behat/data_generators.feature +++ b/admin/tool/behat/tests/behat/data_generators.feature @@ -412,8 +412,8 @@ Feature: Set up contextual data for tests Then I should see "Test Outcome Grade Item 1" And I click on grade item menu "Test Outcome Grade Item 1" of type "gradeitem" on "setup" page And I choose "Edit grade item" in the open action menu + And I click on "Show more..." "link" in the "Edit outcome item" "dialogue" And the field "Outcome" matches value "Grade outcome 1" - And I expand all fieldsets And I should see "Grade category 1" in the "Grade category" "form_row" And I press "Cancel" diff --git a/grade/amd/build/gradebooksetup_forms.min.js b/grade/amd/build/gradebooksetup_forms.min.js index c29efb39bea..fb76c9a8593 100644 --- a/grade/amd/build/gradebooksetup_forms.min.js +++ b/grade/amd/build/gradebooksetup_forms.min.js @@ -5,6 +5,6 @@ define("core_grades/gradebooksetup_forms",["exports","core_form/modalform","core * @module core_grades * @copyright 2023 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU Public License - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modalform=_interopRequireDefault(_modalform),_notification=_interopRequireDefault(_notification);_exports.init=()=>{document.addEventListener("click",(event=>{const args={};let formClass=null,title=null,trigger=null;if(event.target.closest('[data-trigger="add-item-form"]')?(event.preventDefault(),trigger=event.target.closest('[data-trigger="add-item-form"]'),formClass="core_grades\\form\\add_item",title="-1"===trigger.getAttribute("data-itemid")?(0,_str.get_string)("newitem","core_grades"):(0,_str.get_string)("itemsedit","core_grades"),args.itemid=trigger.getAttribute("data-itemid")):event.target.closest('[data-trigger="add-category-form"]')&&(event.preventDefault(),trigger=event.target.closest('[data-trigger="add-category-form"]'),formClass="core_grades\\form\\add_category",title="-1"===trigger.getAttribute("data-category")?(0,_str.get_string)("newcategory","core_grades"):(0,_str.get_string)("categoryedit","core_grades"),args.category=trigger.getAttribute("data-category")),trigger){args.courseid=trigger.getAttribute("data-courseid"),args.gpr_plugin=trigger.getAttribute("data-gprplugin");const modalForm=new _modalform.default({modalConfig:{title:title},formClass:formClass,args:args,saveButtonText:(0,_str.get_string)("save","core"),returnFocus:trigger});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(event=>{event.detail.result?window.location.assign(event.detail.url):_notification.default.addNotification({type:"error",message:(0,_str.get_string)("saving_failed","core_grades")})})),modalForm.show()}}))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modalform=_interopRequireDefault(_modalform),_notification=_interopRequireDefault(_notification);_exports.init=()=>{document.addEventListener("click",(event=>{const args={};let formClass=null,title=null,trigger=null;if(event.target.closest('[data-trigger="add-item-form"]')?(event.preventDefault(),trigger=event.target.closest('[data-trigger="add-item-form"]'),formClass="core_grades\\form\\add_item",title="-1"===trigger.getAttribute("data-itemid")?(0,_str.get_string)("newitem","core_grades"):(0,_str.get_string)("itemsedit","core_grades"),args.itemid=trigger.getAttribute("data-itemid")):event.target.closest('[data-trigger="add-category-form"]')?(event.preventDefault(),trigger=event.target.closest('[data-trigger="add-category-form"]'),formClass="core_grades\\form\\add_category",title="-1"===trigger.getAttribute("data-category")?(0,_str.get_string)("newcategory","core_grades"):(0,_str.get_string)("categoryedit","core_grades"),args.category=trigger.getAttribute("data-category")):event.target.closest('[data-trigger="add-outcome-form"]')&&(event.preventDefault(),trigger=event.target.closest('[data-trigger="add-outcome-form"]'),formClass="core_grades\\form\\add_outcome",title="-1"===trigger.getAttribute("data-itemid")?(0,_str.get_string)("newoutcomeitem","core_grades"):(0,_str.get_string)("outcomeitemsedit","core_grades"),args.itemid=trigger.getAttribute("data-itemid")),trigger){args.courseid=trigger.getAttribute("data-courseid"),args.gpr_plugin=trigger.getAttribute("data-gprplugin");const modalForm=new _modalform.default({modalConfig:{title:title},formClass:formClass,args:args,saveButtonText:(0,_str.get_string)("save","core"),returnFocus:trigger});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(event=>{event.detail.result?window.location.assign(event.detail.url):_notification.default.addNotification({type:"error",message:(0,_str.get_string)("saving_failed","core_grades")})})),modalForm.show()}}))}})); //# sourceMappingURL=gradebooksetup_forms.min.js.map \ No newline at end of file diff --git a/grade/amd/build/gradebooksetup_forms.min.js.map b/grade/amd/build/gradebooksetup_forms.min.js.map index 19cc8439138..4195be79006 100644 --- a/grade/amd/build/gradebooksetup_forms.min.js.map +++ b/grade/amd/build/gradebooksetup_forms.min.js.map @@ -1 +1 @@ -{"version":3,"file":"gradebooksetup_forms.min.js","sources":["../src/gradebooksetup_forms.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 * Prints the add item gradebook form\n *\n * @module core_grades\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU Public License\n */\n\nimport ModalForm from 'core_form/modalform';\nimport {get_string as getString} from 'core/str';\nimport Notification from 'core/notification';\n\n/**\n * Initialize module\n */\nexport const init = () => {\n // Sometimes the trigger does not exist, so lets conditionally add it.\n document.addEventListener('click', event => {\n const args = {};\n\n let formClass = null;\n let title = null;\n let trigger = null;\n if (event.target.closest('[data-trigger=\"add-item-form\"]')) {\n event.preventDefault();\n trigger = event.target.closest('[data-trigger=\"add-item-form\"]');\n formClass = 'core_grades\\\\form\\\\add_item';\n title = trigger.getAttribute('data-itemid') === '-1' ?\n getString('newitem', 'core_grades') : getString('itemsedit', 'core_grades');\n args.itemid = trigger.getAttribute('data-itemid');\n } else if (event.target.closest('[data-trigger=\"add-category-form\"]')) {\n event.preventDefault();\n trigger = event.target.closest('[data-trigger=\"add-category-form\"]');\n formClass = 'core_grades\\\\form\\\\add_category';\n title = trigger.getAttribute('data-category') === '-1' ?\n getString('newcategory', 'core_grades') : getString('categoryedit', 'core_grades');\n args.category = trigger.getAttribute('data-category');\n }\n\n if (trigger) {\n args.courseid = trigger.getAttribute('data-courseid');\n args.gpr_plugin = trigger.getAttribute('data-gprplugin');\n\n const modalForm = new ModalForm({\n modalConfig: {\n title: title,\n },\n formClass: formClass,\n args: args,\n saveButtonText: getString('save', 'core'),\n returnFocus: trigger,\n });\n\n // Show a toast notification when the form is submitted.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, event => {\n if (event.detail.result) {\n window.location.assign(event.detail.url);\n } else {\n Notification.addNotification({\n type: 'error',\n message: getString('saving_failed', 'core_grades')\n });\n }\n });\n\n modalForm.show();\n }\n\n });\n};\n"],"names":["document","addEventListener","event","args","formClass","title","trigger","target","closest","preventDefault","getAttribute","itemid","category","courseid","gpr_plugin","modalForm","ModalForm","modalConfig","saveButtonText","returnFocus","events","FORM_SUBMITTED","detail","result","window","location","assign","url","addNotification","type","message","show"],"mappings":";;;;;;;kMA8BoB,KAEhBA,SAASC,iBAAiB,SAASC,cACzBC,KAAO,OAETC,UAAY,KACZC,MAAQ,KACRC,QAAU,QACVJ,MAAMK,OAAOC,QAAQ,mCACrBN,MAAMO,iBACNH,QAAUJ,MAAMK,OAAOC,QAAQ,kCAC/BJ,UAAY,8BACZC,MAAgD,OAAxCC,QAAQI,aAAa,gBACzB,mBAAU,UAAW,gBAAiB,mBAAU,YAAa,eACjEP,KAAKQ,OAASL,QAAQI,aAAa,gBAC5BR,MAAMK,OAAOC,QAAQ,wCAC5BN,MAAMO,iBACNH,QAAUJ,MAAMK,OAAOC,QAAQ,sCAC/BJ,UAAY,kCACZC,MAAkD,OAA1CC,QAAQI,aAAa,kBACzB,mBAAU,cAAe,gBAAiB,mBAAU,eAAgB,eACxEP,KAAKS,SAAWN,QAAQI,aAAa,kBAGrCJ,QAAS,CACTH,KAAKU,SAAWP,QAAQI,aAAa,iBACrCP,KAAKW,WAAaR,QAAQI,aAAa,wBAEjCK,UAAY,IAAIC,mBAAU,CAC5BC,YAAa,CACTZ,MAAOA,OAEXD,UAAWA,UACXD,KAAMA,KACNe,gBAAgB,mBAAU,OAAQ,QAClCC,YAAab,UAIjBS,UAAUd,iBAAiBc,UAAUK,OAAOC,gBAAgBnB,QACpDA,MAAMoB,OAAOC,OACbC,OAAOC,SAASC,OAAOxB,MAAMoB,OAAOK,2BAEvBC,gBAAgB,CACzBC,KAAM,QACNC,SAAS,mBAAU,gBAAiB,oBAKhDf,UAAUgB"} \ No newline at end of file +{"version":3,"file":"gradebooksetup_forms.min.js","sources":["../src/gradebooksetup_forms.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 * Prints the add item gradebook form\n *\n * @module core_grades\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU Public License\n */\n\nimport ModalForm from 'core_form/modalform';\nimport {get_string as getString} from 'core/str';\nimport Notification from 'core/notification';\n\n/**\n * Initialize module\n */\nexport const init = () => {\n // Sometimes the trigger does not exist, so lets conditionally add it.\n document.addEventListener('click', event => {\n const args = {};\n\n let formClass = null;\n let title = null;\n let trigger = null;\n if (event.target.closest('[data-trigger=\"add-item-form\"]')) {\n event.preventDefault();\n trigger = event.target.closest('[data-trigger=\"add-item-form\"]');\n formClass = 'core_grades\\\\form\\\\add_item';\n title = trigger.getAttribute('data-itemid') === '-1' ?\n getString('newitem', 'core_grades') : getString('itemsedit', 'core_grades');\n args.itemid = trigger.getAttribute('data-itemid');\n } else if (event.target.closest('[data-trigger=\"add-category-form\"]')) {\n event.preventDefault();\n trigger = event.target.closest('[data-trigger=\"add-category-form\"]');\n formClass = 'core_grades\\\\form\\\\add_category';\n title = trigger.getAttribute('data-category') === '-1' ?\n getString('newcategory', 'core_grades') : getString('categoryedit', 'core_grades');\n args.category = trigger.getAttribute('data-category');\n } else if (event.target.closest('[data-trigger=\"add-outcome-form\"]')) {\n event.preventDefault();\n trigger = event.target.closest('[data-trigger=\"add-outcome-form\"]');\n formClass = 'core_grades\\\\form\\\\add_outcome';\n title = trigger.getAttribute('data-itemid') === '-1' ?\n getString('newoutcomeitem', 'core_grades') : getString('outcomeitemsedit', 'core_grades');\n args.itemid = trigger.getAttribute('data-itemid');\n }\n\n if (trigger) {\n args.courseid = trigger.getAttribute('data-courseid');\n args.gpr_plugin = trigger.getAttribute('data-gprplugin');\n\n const modalForm = new ModalForm({\n modalConfig: {\n title: title,\n },\n formClass: formClass,\n args: args,\n saveButtonText: getString('save', 'core'),\n returnFocus: trigger,\n });\n\n // Show a toast notification when the form is submitted.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, event => {\n if (event.detail.result) {\n window.location.assign(event.detail.url);\n } else {\n Notification.addNotification({\n type: 'error',\n message: getString('saving_failed', 'core_grades')\n });\n }\n });\n\n modalForm.show();\n }\n\n });\n};\n"],"names":["document","addEventListener","event","args","formClass","title","trigger","target","closest","preventDefault","getAttribute","itemid","category","courseid","gpr_plugin","modalForm","ModalForm","modalConfig","saveButtonText","returnFocus","events","FORM_SUBMITTED","detail","result","window","location","assign","url","addNotification","type","message","show"],"mappings":";;;;;;;kMA8BoB,KAEhBA,SAASC,iBAAiB,SAASC,cACzBC,KAAO,OAETC,UAAY,KACZC,MAAQ,KACRC,QAAU,QACVJ,MAAMK,OAAOC,QAAQ,mCACrBN,MAAMO,iBACNH,QAAUJ,MAAMK,OAAOC,QAAQ,kCAC/BJ,UAAY,8BACZC,MAAgD,OAAxCC,QAAQI,aAAa,gBACzB,mBAAU,UAAW,gBAAiB,mBAAU,YAAa,eACjEP,KAAKQ,OAASL,QAAQI,aAAa,gBAC5BR,MAAMK,OAAOC,QAAQ,uCAC5BN,MAAMO,iBACNH,QAAUJ,MAAMK,OAAOC,QAAQ,sCAC/BJ,UAAY,kCACZC,MAAkD,OAA1CC,QAAQI,aAAa,kBACzB,mBAAU,cAAe,gBAAiB,mBAAU,eAAgB,eACxEP,KAAKS,SAAWN,QAAQI,aAAa,kBAC9BR,MAAMK,OAAOC,QAAQ,uCAC5BN,MAAMO,iBACNH,QAAUJ,MAAMK,OAAOC,QAAQ,qCAC/BJ,UAAY,iCACZC,MAAgD,OAAxCC,QAAQI,aAAa,gBACzB,mBAAU,iBAAkB,gBAAiB,mBAAU,mBAAoB,eAC/EP,KAAKQ,OAASL,QAAQI,aAAa,gBAGnCJ,QAAS,CACTH,KAAKU,SAAWP,QAAQI,aAAa,iBACrCP,KAAKW,WAAaR,QAAQI,aAAa,wBAEjCK,UAAY,IAAIC,mBAAU,CAC5BC,YAAa,CACTZ,MAAOA,OAEXD,UAAWA,UACXD,KAAMA,KACNe,gBAAgB,mBAAU,OAAQ,QAClCC,YAAab,UAIjBS,UAAUd,iBAAiBc,UAAUK,OAAOC,gBAAgBnB,QACpDA,MAAMoB,OAAOC,OACbC,OAAOC,SAASC,OAAOxB,MAAMoB,OAAOK,2BAEvBC,gBAAgB,CACzBC,KAAM,QACNC,SAAS,mBAAU,gBAAiB,oBAKhDf,UAAUgB"} \ No newline at end of file diff --git a/grade/amd/src/gradebooksetup_forms.js b/grade/amd/src/gradebooksetup_forms.js index 6934b61e623..af0a81e5328 100644 --- a/grade/amd/src/gradebooksetup_forms.js +++ b/grade/amd/src/gradebooksetup_forms.js @@ -50,6 +50,13 @@ export const init = () => { title = trigger.getAttribute('data-category') === '-1' ? getString('newcategory', 'core_grades') : getString('categoryedit', 'core_grades'); args.category = trigger.getAttribute('data-category'); + } else if (event.target.closest('[data-trigger="add-outcome-form"]')) { + event.preventDefault(); + trigger = event.target.closest('[data-trigger="add-outcome-form"]'); + formClass = 'core_grades\\form\\add_outcome'; + title = trigger.getAttribute('data-itemid') === '-1' ? + getString('newoutcomeitem', 'core_grades') : getString('outcomeitemsedit', 'core_grades'); + args.itemid = trigger.getAttribute('data-itemid'); } if (trigger) { diff --git a/grade/classes/form/add_item.php b/grade/classes/form/add_item.php index f4871e395b7..37c04c53469 100644 --- a/grade/classes/form/add_item.php +++ b/grade/classes/form/add_item.php @@ -54,14 +54,8 @@ class add_item extends dynamic_form { $id = $this->optional_param('itemid', null, PARAM_INT); if ($gradeitem = grade_item::fetch(['id' => $id, 'courseid' => $courseid])) { - // Redirect if outcomeid present. - if (!empty($gradeitem->outcomeid) && !empty($CFG->enableoutcomes)) { - $url = new moodle_url('/grade/edit/tree/outcomeitem.php', ['id' => $id, 'courseid' => $courseid]); - redirect($this->gpr->add_url_params($url)); - } $item = $gradeitem->get_record_data(); $parentcategory = $gradeitem->get_parent_category(); - } else { $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); $item = $gradeitem->get_record_data(); @@ -245,57 +239,46 @@ class add_item extends dynamic_form { $gradeitem->parent_category = $parentcategory; } - if ($gradeitem->is_outcome_item()) { - // We have to prevent incompatible modifications of outcomes if outcomes disabled. - $mform->removeElement('grademax'); + if ($gradeitem->is_external_item()) { + // Following items are set up from modules and should not be overrided by user. if ($mform->elementExists('grademin')) { - $mform->removeElement('grademin'); + // The site setting grade_report_showmin may have prevented grademin being added to the form. + $mform->hardFreeze('grademin'); } - $mform->removeElement('gradetype'); - $mform->hardFreeze('scaleid'); + $mform->hardFreeze('itemname,gradetype,grademax,scaleid'); - } else { - if ($gradeitem->is_external_item()) { - // Following items are set up from modules and should not be overrided by user. + // For external items we can not change the grade type, even if no grades exist, so if it is set to + // scale, then remove the grademax and grademin fields from the form - no point displaying them. + if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { + $mform->removeElement('grademax'); if ($mform->elementExists('grademin')) { - // The site setting grade_report_showmin may have prevented grademin being added to the form. - $mform->hardFreeze('grademin'); + $mform->removeElement('grademin'); } - $mform->hardFreeze('itemname,gradetype,grademax,scaleid'); - - // For external items we can not change the grade type, even if no grades exist, so if it is set to - // scale, then remove the grademax and grademin fields from the form - no point displaying them. - if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { - $mform->removeElement('grademax'); - if ($mform->elementExists('grademin')) { - $mform->removeElement('grademin'); - } - } else { // Not using scale, so remove it. - $mform->removeElement('scaleid'); - } - - // Always remove the rescale grades element if it's an external item. - $mform->removeElement('rescalegrades'); - } else if ($gradeitem->has_grades()) { - // Can't change the grade type or the scale if there are grades. - $mform->hardFreeze('gradetype, scaleid'); - - // If we are using scales then remove the unnecessary rescale and grade fields. - if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { - $mform->removeElement('rescalegrades'); - $mform->removeElement('grademax'); - if ($mform->elementExists('grademin')) { - $mform->removeElement('grademin'); - } - } else { // Remove the scale field. - $mform->removeElement('scaleid'); - // Set the maximum grade to disabled unless a grade is chosen. - $mform->hideIf('grademax', 'rescalegrades', 'eq', ''); - } - } else { - // Remove rescale element if there are no grades. - $mform->removeElement('rescalegrades'); + } else { // Not using scale, so remove it. + $mform->removeElement('scaleid'); } + + // Always remove the rescale grades element if it's an external item. + $mform->removeElement('rescalegrades'); + } else if ($gradeitem->has_grades()) { + // Can't change the grade type or the scale if there are grades. + $mform->hardFreeze('gradetype, scaleid'); + + // If we are using scales then remove the unnecessary rescale and grade fields. + if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { + $mform->removeElement('rescalegrades'); + $mform->removeElement('grademax'); + if ($mform->elementExists('grademin')) { + $mform->removeElement('grademin'); + } + } else { // Remove the scale field. + $mform->removeElement('scaleid'); + // Set the maximum grade to disabled unless a grade is chosen. + $mform->hideIf('grademax', 'rescalegrades', 'eq', ''); + } + } else { + // Remove rescale element if there are no grades. + $mform->removeElement('rescalegrades'); } // If we wanted to change parent of existing item - we would have to verify there are no circular references in parents!!! diff --git a/grade/classes/form/add_outcome.php b/grade/classes/form/add_outcome.php new file mode 100644 index 00000000000..5a6ecc967e5 --- /dev/null +++ b/grade/classes/form/add_outcome.php @@ -0,0 +1,494 @@ +. + +namespace core_grades\form; + +defined('MOODLE_INTERNAL') || die; + +use context; +use context_course; +use core_form\dynamic_form; +use grade_category; +use grade_item; +use grade_outcome; +use grade_plugin_return; +use moodle_url; + +require_once($CFG->dirroot.'/grade/lib.php'); + +/** + * Prints the add outcome gradebook form. + * + * @copyright 2023 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + * @package core_grades + */ +class add_outcome extends dynamic_form { + + /** Grade plugin return tracking object. + * @var object $gpr + */ + public $gpr; + + /** + * Helper function to grab the current grade outcome item based on information within the form. + * + * @return array + * @throws \moodle_exception + */ + private function get_gradeitem(): array { + $courseid = $this->optional_param('courseid', null, PARAM_INT); + $id = $this->optional_param('itemid', null, PARAM_INT); + + if ($gradeitem = grade_item::fetch(['id' => $id, 'courseid' => $courseid])) { + // Redirect if outcomeid not present. + if (empty($gradeitem->outcomeid)) { + $url = new moodle_url('/grade/edit/tree/item.php', ['id' => $id, 'courseid' => $courseid]); + redirect($this->gpr->add_url_params($url)); + } + $item = $gradeitem->get_record_data(); + $parentcategory = $gradeitem->get_parent_category(); + if ($item->itemtype == 'mod') { + $cm = get_coursemodule_from_instance($item->itemmodule, $item->iteminstance, $item->courseid); + $item->cmid = $cm->id; + } else { + $item->cmid = 0; + } + } else { + $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); + $item = $gradeitem->get_record_data(); + $parentcategory = grade_category::fetch_course_category($courseid); + } + $item->parentcategory = $parentcategory->id; + + if ($item->hidden > 1) { + $item->hiddenuntil = $item->hidden; + $item->hidden = 0; + } else { + $item->hiddenuntil = 0; + } + + $item->locked = !empty($item->locked); + + if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM || $parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { + $item->aggregationcoef = $item->aggregationcoef == 0 ? 0 : 1; + } else { + $item->aggregationcoef = format_float($item->aggregationcoef, 4); + } + if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { + $item->aggregationcoef2 = format_float($item->aggregationcoef2 * 100.0); + } + $item->cancontrolvisibility = $gradeitem->can_control_visibility(); + return [ + 'gradeitem' => $gradeitem, + 'item' => $item + ]; + } + + /** + * Form definition + * + * @return void + * @throws \coding_exception + * @throws \dml_exception + * @throws \moodle_exception + */ + protected function definition() { + $courseid = $this->optional_param('courseid', null, PARAM_INT); + $id = $this->optional_param('itemid', 0, PARAM_INT); + $gprplugin = $this->optional_param('gpr_plugin', '', PARAM_TEXT); + + if ($gprplugin && ($gprplugin !== 'tree')) { + $this->gpr = new grade_plugin_return(['type' => 'report', 'plugin' => $gprplugin, 'courseid' => $courseid]); + } else { + $this->gpr = new grade_plugin_return(['type' => 'edit', 'plugin' => 'tree', 'courseid' => $courseid]); + } + + $mform =& $this->_form; + + $local = $this->get_gradeitem(); + $gradeitem = $local['gradeitem']; + $item = $local['item']; + + // Hidden elements. + $mform->addElement('hidden', 'id', 0); + $mform->setType('id', PARAM_INT); + $mform->addElement('hidden', 'courseid', $courseid); + $mform->setType('courseid', PARAM_INT); + $mform->addElement('hidden', 'itemid', $id); + $mform->setType('itemid', PARAM_INT); + + // Allow setting of outcomes on module items too. + $outcomeoptions = []; + if ($outcomes = grade_outcome::fetch_all_available($courseid)) { + foreach ($outcomes as $outcome) { + $outcomeoptions[$outcome->id] = $outcome->get_name(); + } + } + + // Visible elements. + $mform->addElement('text', 'itemname', get_string('itemname', 'grades')); + $mform->addRule('itemname', get_string('required'), 'required', null, 'client'); + $mform->setType('itemname', PARAM_TEXT); + + $mform->addElement('selectwithlink', 'outcomeid', get_string('outcome', 'grades'), $outcomeoptions); + $mform->addHelpButton('outcomeid', 'outcome', 'grades'); + $mform->addRule('outcomeid', get_string('required'), 'required'); + + $options = [0 => get_string('none')]; + if ($coursemods = get_course_mods($courseid)) { + foreach ($coursemods as $coursemod) { + if ($mod = get_coursemodule_from_id($coursemod->modname, $coursemod->id)) { + $options[$coursemod->id] = format_string($mod->name); + } + } + } + $mform->addElement('select', 'cmid', get_string('linkedactivity', 'grades'), $options); + $mform->addHelpButton('cmid', 'linkedactivity', 'grades'); + $mform->setDefault('cmid', 0); + + // Hiding. + $mform->addElement('checkbox', 'hidden', get_string('hidden', 'grades')); + $mform->addHelpButton('hidden', 'hidden', 'grades'); + + // Locking. + $mform->addElement('advcheckbox', 'locked', get_string('locked', 'grades')); + $mform->addHelpButton('locked', 'locked', 'grades'); + + // Parent category related settings. + $mform->addElement('advcheckbox', 'weightoverride', get_string('adjustedweight', 'grades')); + $mform->addHelpButton('weightoverride', 'weightoverride', 'grades'); + + $mform->addElement('text', 'aggregationcoef2', get_string('weight', 'grades')); + $mform->addHelpButton('aggregationcoef2', 'weight', 'grades'); + $mform->setType('aggregationcoef2', PARAM_RAW); + $mform->hideIf('aggregationcoef2', 'weightoverride'); + + $options = []; + $coefstring = ''; + $categories = grade_category::fetch_all(['courseid' => $courseid]); + foreach ($categories as $cat) { + $cat->apply_forced_settings(); + $options[$cat->id] = $cat->get_name(); + if ($cat->is_aggregationcoef_used()) { + if ($cat->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { + $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefweight') ? + 'aggregationcoefweight' : 'aggregationcoef'; + } else if ($cat->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { + $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextrasum') ? + 'aggregationcoefextrasum' : 'aggregationcoef'; + } else if ($cat->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { + $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextraweight') ? + 'aggregationcoefextraweight' : 'aggregationcoef'; + } else if ($cat->aggregation == GRADE_AGGREGATE_SUM) { + $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextrasum') ? + 'aggregationcoefextrasum' : 'aggregationcoef'; + } else { + $coefstring = 'aggregationcoef'; + } + } else { + $mform->disabledIf('aggregationcoef', 'parentcategory', 'eq', $cat->id); + } + } + + if (count($categories) > 1) { + $mform->addElement('select', 'parentcategory', get_string('gradecategory', 'grades'), $options); + $mform->disabledIf('parentcategory', 'cmid', 'noteq', 0); + } + + if ($coefstring !== '') { + if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') { + $coefstring = 'aggregationcoefextrasum'; + $mform->addElement('checkbox', 'aggregationcoef', get_string($coefstring, 'grades')); + } else { + $mform->addElement('text', 'aggregationcoef', get_string($coefstring, 'grades')); + } + $mform->addHelpButton('aggregationcoef', $coefstring, 'grades'); + } + + // Remove the aggregation coef element if not needed. + if ($gradeitem->is_course_item()) { + if ($mform->elementExists('parentcategory')) { + $mform->removeElement('parentcategory'); + } + if ($mform->elementExists('aggregationcoef')) { + $mform->removeElement('aggregationcoef'); + } + + } else { + // If we wanted to change parent of existing item - we would have to verify there are no circular references in parents. + if ($id > -1 && $mform->elementExists('parentcategory')) { + $mform->hardFreeze('parentcategory'); + } + + $parentcategory = $gradeitem->get_parent_category(); + if (!$parentcategory) { + // If we do not have an id, we are creating a new grade item. + + // Assign the course category to this grade item. + $parentcategory = grade_category::fetch_course_category($courseid); + $gradeitem->parent_category = $parentcategory; + } + + $parentcategory->apply_forced_settings(); + + if (!$parentcategory->is_aggregationcoef_used() || !$parentcategory->aggregateoutcomes) { + if ($mform->elementExists('aggregationcoef')) { + $mform->removeElement('aggregationcoef'); + } + } else { + // Fix label if needed. + $agg_el =& $mform->getElement('aggregationcoef'); + $aggcoef = ''; + if ($parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { + $aggcoef = 'aggregationcoefweight'; + + } else if ($parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { + $aggcoef = 'aggregationcoefextrasum'; + + } else if ($parentcategory->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { + $aggcoef = 'aggregationcoefextraweight'; + + } else if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { + $aggcoef = 'aggregationcoefextrasum'; + } + + if ($aggcoef !== '') { + $agg_el->setLabel(get_string($aggcoef, 'grades')); + $mform->addHelpButton('aggregationcoef', $aggcoef, 'grades'); + } + } + + // Remove the natural weighting fields for other aggregations, + // or when the category does not aggregate outcomes. + if ($parentcategory->aggregation != GRADE_AGGREGATE_SUM || + !$parentcategory->aggregateoutcomes) { + if ($mform->elementExists('weightoverride')) { + $mform->removeElement('weightoverride'); + } + if ($mform->elementExists('aggregationcoef2')) { + $mform->removeElement('aggregationcoef2'); + } + } + } + + $url = new moodle_url('/grade/edit/tree/outcomeitem.php', ['id' => $id, 'courseid' => $courseid]); + $url = $this->gpr->add_url_params($url); + $url = '' . get_string('showmore', 'form') .''; + $mform->addElement('static', 'advancedform', $url); + + // Add return tracking info. + $this->gpr->add_mform_elements($mform); + + $this->set_data($item); + } + + /** + * Return form context + * + * @return context + */ + protected function get_context_for_dynamic_submission(): context { + $courseid = $this->optional_param('courseid', null, PARAM_INT); + return context_course::instance($courseid); + } + + /** + * Check if current user has access to this form, otherwise throw exception + * + * @return void + * @throws \required_capability_exception + */ + protected function check_access_for_dynamic_submission(): void { + $courseid = $this->optional_param('courseid', null, PARAM_INT); + require_capability('moodle/grade:manage', context_course::instance($courseid)); + } + + /** + * Load in existing data as form defaults + * + * @return void + */ + public function set_data_for_dynamic_submission(): void { + $this->set_data((object)[ + 'courseid' => $this->optional_param('courseid', null, PARAM_INT), + 'itemid' => $this->optional_param('itemid', null, PARAM_INT) + ]); + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + * @throws \moodle_exception + */ + protected function get_page_url_for_dynamic_submission(): moodle_url { + $params = [ + 'id' => $this->optional_param('courseid', null, PARAM_INT), + 'itemid' => $this->optional_param('itemid', null, PARAM_INT), + ]; + return new moodle_url('/grade/edit/tree/index.php', $params); + } + + /** + * Process the form submission, used if form was submitted via AJAX + * + * @return array + * @throws \moodle_exception + */ + public function process_dynamic_submission() { + global $DB; + $data = $this->get_data(); + + $url = $this->gpr->get_return_url('index.php?id=' . $data->courseid); + $local = $this->get_gradeitem(); + $gradeitem = $local['gradeitem']; + $item = $local['item']; + $parentcategory = grade_category::fetch_course_category($data->courseid); + + // Form submission handling. + // If unset, give the aggregation values a default based on parent aggregation method. + $defaults = grade_category::get_default_aggregation_coefficient_values($parentcategory->aggregation); + if (!isset($data->aggregationcoef) || $data->aggregationcoef == '') { + $data->aggregationcoef = $defaults['aggregationcoef']; + } + if (!isset($data->weightoverride)) { + $data->weightoverride = $defaults['weightoverride']; + } + + if (property_exists($data, 'calculation')) { + $data->calculation = grade_item::normalize_formula($data->calculation, $data->courseid); + } + + $hide = empty($data->hiddenuntil) ? 0 : $data->hiddenuntil; + if (!$hide) { + $hide = empty($data->hidden) ? 0 : $data->hidden; + } + + $locked = empty($data->locked) ? 0 : $data->locked; + $locktime = empty($data->locktime) ? 0 : $data->locktime; + + $convert = ['gradepass', 'aggregationcoef', 'aggregationcoef2']; + foreach ($convert as $param) { + if (property_exists($data, $param)) { + $data->$param = unformat_float($data->$param); + } + } + if (isset($data->aggregationcoef2) && $parentcategory->aggregation == GRADE_AGGREGATE_SUM) { + $data->aggregationcoef2 = $data->aggregationcoef2 / 100.0; + } else { + $data->aggregationcoef2 = $defaults['aggregationcoef2']; + } + + grade_item::set_properties($gradeitem, $data); + + // Link this outcome item to the user specified linked activity. + if (empty($data->cmid) || $data->cmid == 0) { + // Manual item. + $gradeitem->itemtype = 'manual'; + $gradeitem->itemmodule = null; + $gradeitem->iteminstance = null; + $gradeitem->itemnumber = 0; + + } else { + $params = [$data->cmid]; + $module = $DB->get_record_sql("SELECT cm.*, m.name as modname + FROM {modules} m, {course_modules} cm + WHERE cm.id = ? AND cm.module = m.id ", $params); + $gradeitem->itemtype = 'mod'; + $gradeitem->itemmodule = $module->modname; + $gradeitem->iteminstance = $module->instance; + + if ($items = grade_item::fetch_all(['itemtype' => 'mod', 'itemmodule' => $gradeitem->itemmodule, + 'iteminstance' => $gradeitem->iteminstance, 'courseid' => $data->courseid])) { + if (!empty($gradeitem->id) && in_array($gradeitem, $items)) { + // No change needed. + } else { + $max = 999; + foreach ($items as $item) { + if (empty($item->outcomeid)) { + continue; + } + if ($item->itemnumber > $max) { + $max = $item->itemnumber; + } + } + $gradeitem->itemnumber = $max + 1; + } + } else { + $gradeitem->itemnumber = 1000; + } + } + + // Fix scale used. + $outcome = grade_outcome::fetch(['id' => $data->outcomeid]); + $gradeitem->gradetype = GRADE_TYPE_SCALE; + $gradeitem->scaleid = $outcome->scaleid; // TODO: we might recalculate existing outcome grades when changing scale. + + if (empty($gradeitem->id)) { + $gradeitem->insert(); + // Move next to activity if adding linked outcome. + if ($gradeitem->itemtype == 'mod') { + if ($linkeditem = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => $gradeitem->itemmodule, + 'iteminstance' => $gradeitem->iteminstance, 'itemnumber' => 0, 'courseid' => $data->courseid])) { + $gradeitem->set_parent($linkeditem->categoryid); + $gradeitem->move_after_sortorder($linkeditem->sortorder); + } + } else { + // Set parent if needed. + if (isset($data->parentcategory)) { + $gradeitem->set_parent($data->parentcategory, false); + } + } + + } else { + $gradeitem->update(); + } + + if ($item->cancontrolvisibility) { + // Update hiding flag. + $gradeitem->set_hidden($hide, true); + } + + $gradeitem->set_locktime($locktime); // Locktime first - it might be removed when unlocking. + $gradeitem->set_locked($locked, false, true); + return [ + 'result' => true, + 'url' => $url, + 'errors' => [], + ]; + } + + /** + * Form validation. + * + * @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): array { + $errors = []; + $local = $this->get_gradeitem(); + $gradeitem = $local['gradeitem']; + $item = $local['item']; + + if (!grade_verify_idnumber($gradeitem->id, $item->courseid, $gradeitem)) { + $errors['idnumber'] = get_string('idnumbertaken'); + } + return $errors; + } +} diff --git a/grade/classes/output/gradebook_setup_action_bar.php b/grade/classes/output/gradebook_setup_action_bar.php index 3e8b85e116d..c85c91088a4 100644 --- a/grade/classes/output/gradebook_setup_action_bar.php +++ b/grade/classes/output/gradebook_setup_action_bar.php @@ -73,10 +73,22 @@ class gradebook_setup_action_bar extends action_bar { $data['addgradeitembutton'] = $addgradeitembutton->export_for_template($output); // If outcomes are enabled, add a button to the action bar with a link to the 'add outcome item' page. - if (!empty($CFG->enableoutcomes)) { - $addoutcomeitemlink = new moodle_url('/grade/edit/tree/outcomeitem.php', ['courseid' => $courseid]); - $addoutcomeitembutton = new \single_button($addoutcomeitemlink, get_string('addoutcomeitem', 'grades'), - 'get'); + if (!empty($CFG->enableoutcomes) && count(\grade_outcome::fetch_all_available($courseid)) > 0) { + // Add a button to the action bar with a link to the 'add outcome item' page. + $addoutcomeitem = new moodle_url('#'); + $addoutcomeitembutton = new \single_button( + $addoutcomeitem, + get_string('addoutcomeitem', 'grades'), + 'get', + \single_button::BUTTON_SECONDARY, + [ + 'class' => 'btn btn-secondary', + 'data-courseid' => $courseid, + 'data-itemid' => -1, + 'data-trigger' => 'add-outcome-form', + 'data-gprplugin' => 'tree' + ] + ); $data['addoutcomeitembutton'] = $addoutcomeitembutton->export_for_template($output); } diff --git a/grade/edit/tree/outcomeitem_form.php b/grade/edit/tree/outcomeitem_form.php index 0851c7bd8c2..d72f53bc59c 100644 --- a/grade/edit/tree/outcomeitem_form.php +++ b/grade/edit/tree/outcomeitem_form.php @@ -154,15 +154,6 @@ class edit_outcomeitem_form extends moodleform { $gpr = $this->_customdata['gpr']; $gpr->add_mform_elements($mform); -/// mark advanced according to site settings - if (isset($CFG->grade_item_advanced)) { - $advanced = explode(',', $CFG->grade_item_advanced); - foreach ($advanced as $el) { - if ($mform->elementExists($el)) { - $mform->setAdvanced($el); - } - } - } //------------------------------------------------------------------------------- // buttons $this->add_action_buttons(); diff --git a/grade/lib.php b/grade/lib.php index a23515cc92f..973447b6084 100644 --- a/grade/lib.php +++ b/grade/lib.php @@ -2105,8 +2105,8 @@ class grade_structure { $title = grade_helper::get_lang_string('editgrade', 'grades'); } else if (($element['type'] == 'item') || ($element['type'] == 'categoryitem') || ($element['type'] == 'courseitem')) { + $url = new moodle_url('#'); if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) { - $url = new moodle_url('#'); return html_writer::link($url, get_string('itemsedit', 'grades'), [ 'class' => 'dropdown-item', 'aria-label' => get_string('itemsedit', 'grades'), @@ -2115,12 +2115,16 @@ class grade_structure { 'data-courseid' => $this->courseid, 'data-itemid' => $object->id, 'data-trigger' => 'add-item-form' ]); - } else { - $url = new moodle_url('/grade/edit/tree/outcomeitem.php', - ['courseid' => $this->courseid, 'id' => $object->id]); + } else if (count(grade_outcome::fetch_all_available($this->courseid)) > 0) { + return html_writer::link($url, get_string('itemsedit', 'grades'), [ + 'class' => 'dropdown-item', + get_string('itemsedit', 'grades'), + 'role' => 'menuitem', + 'data-gprplugin' => $gpr->plugin, + 'data-courseid' => $this->courseid, + 'data-itemid' => $object->id, 'data-trigger' => 'add-outcome-form' + ]); } - $url = $gpr->add_url_params($url); - $title = grade_helper::get_lang_string('itemsedit', 'grades'); } else if ($element['type'] == 'category') { $url = new moodle_url('#'); $title = grade_helper::get_lang_string('categoryedit', 'grades'); diff --git a/version.php b/version.php index 4d3a14578d4..58ba5e4854c 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2023080100.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2023080100.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.3dev (Build: 20230728)'; // Human-friendly version name