Merge branch 'MDL-66074-OCT28' of git://github.com/Chocolate-lightning/moodle_forum-project

This commit is contained in:
Adrian Greeve 2019-10-30 13:44:32 +08:00
commit 51cc5e1789
201 changed files with 11752 additions and 459 deletions

View File

@ -1,2 +1,2 @@
define ("core_course/repository",["jquery","core/ajax"],function(a,b){return{getEnrolledCoursesByTimelineClassification:function getEnrolledCoursesByTimelineClassification(a,c,d,e){var f={classification:a};if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.call([{methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:f}])[0]},getLastAccessedCourses:function getLastAccessedCourses(a,c,d,e){var f={};if("undefined"!=typeof a){f.userid=a}if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.call([{methodname:"core_course_get_recent_courses",args:f}])[0]}}});
define ("core_course/repository",["jquery","core/ajax"],function(a,b){return{getEnrolledCoursesByTimelineClassification:function getEnrolledCoursesByTimelineClassification(a,c,d,e){var f={classification:a};if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.call([{methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:f}])[0]},getLastAccessedCourses:function getLastAccessedCourses(a,c,d,e){var f={};if("undefined"!=typeof a){f.userid=a}if("undefined"!=typeof c){f.limit=c}if("undefined"!=typeof d){f.offset=d}if("undefined"!=typeof e){f.sort=e}return b.call([{methodname:"core_course_get_recent_courses",args:f}])[0]},getUsersFromCourseModuleID:function getEnrolledUsersFromCourseModuleID(a){return b.call([{methodname:"core_course_get_enrolled_users_by_cmid",args:{cmid:a}}])[0]}}});
//# sourceMappingURL=repository.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -94,8 +94,26 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
return Ajax.call([request])[0];
};
/**
* Get the list of users enrolled in this cmid.
*
* @param {Number} cmid Course Module from which the users will be obtained
* @returns {Promise} Promise containing a list of users
*/
var getEnrolledUsersFromCourseModuleID = function(cmid) {
var request = {
methodname: 'core_course_get_enrolled_users_by_cmid',
args: {
cmid: cmid,
},
};
return Ajax.call([request])[0];
};
return {
getEnrolledCoursesByTimelineClassification: getEnrolledCoursesByTimelineClassification,
getLastAccessedCourses: getLastAccessedCourses
getLastAccessedCourses: getLastAccessedCourses,
getUsersFromCourseModuleID: getEnrolledUsersFromCourseModuleID,
};
});

View File

@ -4053,4 +4053,82 @@ class core_course_external extends external_api {
public static function get_recent_courses_returns() {
return new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Courses');
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function get_enrolled_users_by_cmid_parameters() {
return new external_function_parameters([
'cmid' => new external_value(PARAM_INT, 'id of the course module', VALUE_REQUIRED),
]);
}
/**
* Get all users in a course for a given cmid.
*
* @param int $cmid Course Module id from which the users will be obtained
* @return array List of users
* @throws invalid_parameter_exception
*/
public static function get_enrolled_users_by_cmid(int $cmid) {
$warnings = [];
[
'cmid' => $cmid,
] = self::validate_parameters(self::get_enrolled_users_by_cmid_parameters(), [
'cmid' => $cmid,
]);
list($course, $cm) = get_course_and_cm_from_cmid($cmid);
$coursecontext = context_course::instance($course->id);
self::validate_context($coursecontext);
$enrolledusers = get_enrolled_users($coursecontext);
$users = array_map(function ($user) {
$user->fullname = fullname($user);
return $user;
}, $enrolledusers);
sort($users);
return [
'users' => $users,
'warnings' => $warnings,
];
}
/**
* Returns description of method result value
*
* @return external_description
*/
public static function get_enrolled_users_by_cmid_returns() {
return new external_single_structure([
'users' => new external_multiple_structure(self::user_description()),
'warnings' => new external_warnings(),
]);
}
/**
* Create user return value description.
*
* @return external_description
*/
public static function user_description() {
$userfields = array(
'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'),
'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL),
'firstname' => new external_value(
core_user::get_property_type('firstname'),
'The first name(s) of the user',
VALUE_OPTIONAL),
'lastname' => new external_value(
core_user::get_property_type('lastname'),
'The family name of the user',
VALUE_OPTIONAL),
);
return new external_single_structure($userfields);
}
}

View File

@ -27,6 +27,8 @@
defined('MOODLE_INTERNAL') || die;
use \core_grades\component_gradeitems;
require_once($CFG->dirroot.'/course/lib.php');
/**
@ -213,49 +215,63 @@ function edit_module_post_actions($moduleinfo, $course) {
$hasgrades = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_HAS_GRADE, false);
$hasoutcomes = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_OUTCOMES, true);
// Sync idnumber with grade_item.
if ($hasgrades && $grade_item = grade_item::fetch(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
'iteminstance'=>$moduleinfo->instance, 'itemnumber'=>0, 'courseid'=>$course->id))) {
$gradeupdate = false;
if ($grade_item->idnumber != $moduleinfo->cmidnumber) {
$grade_item->idnumber = $moduleinfo->cmidnumber;
$gradeupdate = true;
}
if (isset($moduleinfo->gradepass) && $grade_item->gradepass != $moduleinfo->gradepass) {
$grade_item->gradepass = $moduleinfo->gradepass;
$gradeupdate = true;
}
if ($gradeupdate) {
$grade_item->update();
}
}
if ($hasgrades) {
$items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
'iteminstance'=>$moduleinfo->instance, 'courseid'=>$course->id));
} else {
$items = array();
}
$items = grade_item::fetch_all([
'itemtype' => 'mod',
'itemmodule' => $moduleinfo->modulename,
'iteminstance' => $moduleinfo->instance,
'courseid' => $course->id,
]);
// Create parent category if requested and move to correct parent category.
if ($items and isset($moduleinfo->gradecat)) {
if ($moduleinfo->gradecat == -1) {
$grade_category = new grade_category();
$grade_category->courseid = $course->id;
$grade_category->fullname = $moduleinfo->name;
$grade_category->insert();
if ($grade_item) {
$parent = $grade_item->get_parent_category();
$grade_category->set_parent($parent->id);
}
$moduleinfo->gradecat = $grade_category->id;
}
$component = "mod_{$moduleinfo->modulename}";
if ($items) {
foreach ($items as $item) {
$update = false;
foreach ($items as $itemid=>$unused) {
$items[$itemid]->set_parent($moduleinfo->gradecat);
if ($itemid == $grade_item->id) {
// Use updated grade_item.
$grade_item = $items[$itemid];
// Sync idnumber with grade_item.
// Note: This only happens for itemnumber 0 at this time.
if ($item->itemnumber == 0 && ($item->idnumber != $moduleinfo->cmidnumber)) {
$item->idnumber = $moduleinfo->cmidnumber;
$update = true;
}
// Determine the grade category.
$gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $item->itemnumber, 'gradecat');
if (property_exists($moduleinfo, $gradecatfieldname)) {
$gradecat = $moduleinfo->$gradecatfieldname;
if ($gradecat == -1) {
$gradecategory = new grade_category();
$gradecategory->courseid = $course->id;
$gradecategory->fullname = $moduleinfo->name;
$gradecategory->insert();
$parent = $item->get_parent_category();
$gradecategory->set_parent($parent->id);
$gradecat = $gradecategory->id;
}
$oldgradecat = null;
if ($parent = $item->get_parent_category()) {
$oldgradecat = $parent->id;
}
if ($oldgradecat != $gradecat) {
$item->set_parent($gradecat);
$update = true;
}
}
// Determine the gradepass.
$gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $item->itemnumber, 'gradepass');
if (isset($moduleinfo->{$gradepassfieldname})) {
$gradepass = $moduleinfo->{$gradepassfieldname};
if (null !== $gradepass && $gradepass != $item->gradepass) {
$item->gradepass = $gradepass;
$update = true;
}
}
if ($update) {
$item->update();
}
}
}
@ -263,8 +279,6 @@ function edit_module_post_actions($moduleinfo, $course) {
require_once($CFG->libdir.'/grade/grade_outcome.php');
// Add outcomes if requested.
if ($hasoutcomes && $outcomes = grade_outcome::fetch_all_available($course->id)) {
$grade_items = array();
// Outcome grade_item.itemnumber start at 1000, there is nothing above outcomes.
$max_itemnumber = 999;
if ($items) {
@ -279,7 +293,7 @@ function edit_module_post_actions($moduleinfo, $course) {
$elname = 'outcome_'.$outcome->id;
if (property_exists($moduleinfo, $elname) and $moduleinfo->$elname) {
// So we have a request for new outcome grade item?
// Check if this is a new outcome grade item.
if ($items) {
$outcomeexists = false;
foreach($items as $item) {
@ -295,25 +309,25 @@ function edit_module_post_actions($moduleinfo, $course) {
$max_itemnumber++;
$outcome_item = new grade_item();
$outcome_item->courseid = $course->id;
$outcome_item->itemtype = 'mod';
$outcome_item->itemmodule = $moduleinfo->modulename;
$outcome_item->iteminstance = $moduleinfo->instance;
$outcome_item->itemnumber = $max_itemnumber;
$outcome_item->itemname = $outcome->fullname;
$outcome_item->outcomeid = $outcome->id;
$outcome_item->gradetype = GRADE_TYPE_SCALE;
$outcome_item->scaleid = $outcome->scaleid;
$outcome_item->insert();
// Move the new outcome into correct category and fix sortorder if needed.
if ($grade_item) {
$outcome_item->set_parent($grade_item->categoryid);
$outcome_item->move_after_sortorder($grade_item->sortorder);
$outcomeitem = new grade_item();
$outcomeitem->courseid = $course->id;
$outcomeitem->itemtype = 'mod';
$outcomeitem->itemmodule = $moduleinfo->modulename;
$outcomeitem->iteminstance = $moduleinfo->instance;
$outcomeitem->itemnumber = $max_itemnumber;
$outcomeitem->itemname = $outcome->fullname;
$outcomeitem->outcomeid = $outcome->id;
$outcomeitem->gradetype = GRADE_TYPE_SCALE;
$outcomeitem->scaleid = $outcome->scaleid;
$outcomeitem->insert();
if ($items) {
// Move the new outcome into the same category and immediately after the first grade item.
$item = reset($items);
$outcomeitem->set_parent($item->categoryid);
$outcomeitem->move_after_sortorder($item->sortorder);
} else if (isset($moduleinfo->gradecat)) {
$outcome_item->set_parent($moduleinfo->gradecat);
$outcomeitem->set_parent($moduleinfo->gradecat);
}
}
}
@ -354,7 +368,6 @@ function edit_module_post_actions($moduleinfo, $course) {
return $moduleinfo;
}
/**
* Set module info default values for the unset module attributs.
*
@ -702,34 +715,43 @@ function get_moduleinfo_data($cm, $course) {
}
}
if ($items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$data->modulename,
'iteminstance'=>$data->instance, 'courseid'=>$course->id))) {
$component = "mod_{$data->modulename}";
$items = grade_item::fetch_all([
'itemtype' => 'mod',
'itemmodule' => $data->modulename,
'iteminstance' => $data->instance,
'courseid' => $course->id,
]);
if ($items) {
// Add existing outcomes.
foreach ($items as $item) {
if (!empty($item->outcomeid)) {
$data->{'outcome_' . $item->outcomeid} = 1;
} else if (isset($item->gradepass)) {
$decimalpoints = $item->get_decimals();
$data->gradepass = format_float($item->gradepass, $decimalpoints);
$gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $item->itemnumber, 'gradepass');
$data->{$gradepassfieldname} = format_float($item->gradepass, $item->get_decimals());
}
}
// set category if present
$gradecat = false;
$gradecat = [];
foreach ($items as $item) {
if ($gradecat === false) {
$gradecat = $item->categoryid;
continue;
if (!isset($gradecat[$item->itemnumber])) {
$gradecat[$item->itemnumber] = $item->categoryid;
}
if ($gradecat != $item->categoryid) {
//mixed categories
$gradecat = false;
break;
if ($gradecat[$item->itemnumber] != $item->categoryid) {
// Mixed categories.
$gradecat[$item->itemnumber] = false;
}
}
if ($gradecat !== false) {
// do not set if mixed categories present
$data->gradecat = $gradecat;
foreach ($gradecat as $itemnumber => $cat) {
if ($cat !== false) {
$gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat');
// Do not set if mixed categories present.
$data->{$gradecatfieldname} = $cat;
}
}
}
return array($cm, $context, $module, $data, $cw);

View File

@ -1,12 +1,40 @@
<?php
require_once ($CFG->libdir.'/formslib.php');
// 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 <http://www.gnu.org/licenses/>.
/**
* Moodleform.
*
* @package core_course
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/plagiarismlib.php');
use core_grades\component_gradeitems;
/**
* This class adds extra methods to form wrapper specific to be used for module
* add / update forms mod/{modname}/mod_form.php replaced deprecated mod/{modname}/mod.html
* This class adds extra methods to form wrapper specific to be used for module add / update forms
* mod/{modname}/mod_form.php replaced deprecated mod/{modname}/mod.html Moodleform.
*
* @package core_course
* @copyright Andrew Nicols <andrew@nicols.co.uk>
*/
abstract class moodleform_mod extends moodleform {
/** Current data */
@ -68,6 +96,9 @@ abstract class moodleform_mod extends moodleform {
/** @var object The course format of the current course. */
protected $courseformat;
/** @var string Whether this is graded or rated. */
private $gradedorrated = null;
public function __construct($current, $section, $cm, $course) {
global $CFG;
@ -215,18 +246,19 @@ abstract class moodleform_mod extends moodleform {
if ($id = $mform->getElementValue('update')) {
$modulename = $mform->getElementValue('modulename');
$instance = $mform->getElementValue('instance');
$component = "mod_{$modulename}";
if ($this->_features->gradecat) {
$gradecat = false;
if (!empty($CFG->enableoutcomes) and $this->_features->outcomes) {
$outcomes = grade_outcome::fetch_all_available($COURSE->id);
if (!empty($outcomes)) {
$gradecat = true;
}
}
$hasgradeitems = false;
$items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,'iteminstance'=>$instance, 'courseid'=>$COURSE->id));
$items = grade_item::fetch_all([
'itemtype' => 'mod',
'itemmodule' => $modulename,
'iteminstance' => $instance,
'courseid' => $COURSE->id,
]);
$gradecategories = [];
$removecategories = [];
//will be no items if, for example, this activity supports ratings but rating aggregate type == no ratings
if (!empty($items)) {
foreach ($items as $item) {
@ -241,32 +273,23 @@ abstract class moodleform_mod extends moodleform {
}
foreach ($items as $item) {
if (is_bool($gradecat)) {
$gradecat = $item->categoryid;
continue;
}
if ($gradecat != $item->categoryid) {
//mixed categories
$gradecat = false;
break;
$gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber(
$component,
$item->itemnumber,
'gradecat'
);
if (!isset($gradecategories[$gradecatfieldname])) {
$gradecategories[$gradecatfieldname] = $item->categoryid;
} else if ($gradecategories[$gradecatfieldname] != $item->categoryid) {
$removecategories[$gradecatfieldname] = true;
}
}
}
if (!$hasgradeitems && $mform->elementExists('gradepass')) {
// Remove form element 'Grade to pass' since there are no grade items (when rating not selected).
$mform->removeElement('gradepass');
}
if ($gradecat === false) {
// items and outcomes in different categories - remove the option
// TODO: add a "Mixed categories" text instead of removing elements with no explanation
if ($mform->elementExists('gradecat')) {
$mform->removeElement('gradecat');
if ($this->_features->rating && !$mform->elementExists('gradepass')) {
//if supports ratings then the max grade dropdown wasnt added so the grade box can be removed entirely
$mform->removeElement('modstandardgrade');
}
foreach ($removecategories as $toremove) {
if ($mform->elementExists($toremove)) {
$mform->removeElement($toremove);
}
}
}
@ -274,13 +297,13 @@ abstract class moodleform_mod extends moodleform {
if ($COURSE->groupmodeforce) {
if ($mform->elementExists('groupmode')) {
$mform->hardFreeze('groupmode'); // groupmode can not be changed if forced from course settings
// The groupmode can not be changed if forced from course settings.
$mform->hardFreeze('groupmode');
}
}
// Don't disable/remove groupingid if it is currently set to something,
// otherwise you cannot turn it off at same time as turning off other
// option (MDL-30764)
// Don't disable/remove groupingid if it is currently set to something, otherwise you cannot turn it off at same
// time as turning off other option (MDL-30764).
if (empty($this->_cm) || !$this->_cm->groupingid) {
if ($mform->elementExists('groupmode') && empty($COURSE->groupmodeforce)) {
$mform->hideIf('groupingid', 'groupmode', 'eq', NOGROUPS);
@ -386,36 +409,45 @@ abstract class moodleform_mod extends moodleform {
}
}
// Ratings: Don't let them select an aggregate type without selecting a scale.
// If the user has selected to use ratings but has not chosen a scale or set max points then the form is
// invalid. If ratings have been selected then the user must select either a scale or max points.
// This matches (horrible) logic in data_preprocessing.
if (isset($data['assessed']) && $data['assessed'] > 0 && empty($data['scale'])) {
$errors['assessed'] = get_string('scaleselectionrequired', 'rating');
}
$component = "mod_{$this->_modname}";
$itemnames = component_gradeitems::get_itemname_mapping_for_component($component);
foreach ($itemnames as $itemnumber => $itemname) {
$gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade');
$gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradepass');
$assessedfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'assessed');
$scalefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'scale');
// Check that the grade pass is a valid number.
$gradepassvalid = false;
if (isset($data['gradepass'])) {
if (unformat_float($data['gradepass'], true) === false) {
$errors['gradepass'] = get_string('err_numeric', 'form');
} else {
$gradepassvalid = true;
// Ratings: Don't let them select an aggregate type without selecting a scale.
// If the user has selected to use ratings but has not chosen a scale or set max points then the form is
// invalid. If ratings have been selected then the user must select either a scale or max points.
// This matches (horrible) logic in data_preprocessing.
if (isset($data[$assessedfieldname]) && $data[$assessedfieldname] > 0 && empty($data[$scalefieldname])) {
$errors[$assessedfieldname] = get_string('scaleselectionrequired', 'rating');
}
}
// Grade to pass: ensure that the grade to pass is valid for points and scales.
// If we are working with a scale, convert into a positive number for validation.
if ($gradepassvalid && isset($data['gradepass']) && (!empty($data['grade']) || !empty($data['scale']))) {
$scale = !empty($data['grade']) ? $data['grade'] : $data['scale'];
if ($scale < 0) {
$scalevalues = $DB->get_record('scale', array('id' => -$scale));
$grade = count(explode(',', $scalevalues->scale));
} else {
$grade = $scale;
// Check that the grade pass is a valid number.
$gradepassvalid = false;
if (isset($data[$gradepassfieldname])) {
if (unformat_float($data[$gradepassfieldname], true) === false) {
$errors[$gradepassfieldname] = get_string('err_numeric', 'form');
} else {
$gradepassvalid = true;
}
}
if (unformat_float($data['gradepass']) > $grade) {
$errors['gradepass'] = get_string('gradepassgreaterthangrade', 'grades', $grade);
// Grade to pass: ensure that the grade to pass is valid for points and scales.
// If we are working with a scale, convert into a positive number for validation.
if ($gradepassvalid && isset($data[$gradepassfieldname]) && (!empty($data[$gradefieldname]) || !empty($data[$scalefieldname]))) {
$scale = !empty($data[$gradefieldname]) ? $data[$gradefieldname] : $data[$scalefieldname];
if ($scale < 0) {
$scalevalues = $DB->get_record('scale', array('id' => -$scale));
$grade = count(explode(',', $scalevalues->scale));
} else {
$grade = $scale;
}
if (unformat_float($data[$gradepassfieldname]) > $grade) {
$errors[$gradepassfieldname] = get_string('gradepassgreaterthangrade', 'grades', $grade);
}
}
}
@ -485,7 +517,7 @@ abstract class moodleform_mod extends moodleform {
/**
* Adds all the standard elements to a form to edit the settings for an activity module.
*/
function standard_coursemodule_elements(){
protected function standard_coursemodule_elements() {
global $COURSE, $CFG, $DB;
$mform =& $this->_form;
@ -500,70 +532,10 @@ abstract class moodleform_mod extends moodleform {
}
}
if ($this->_features->rating) {
require_once($CFG->dirroot.'/rating/lib.php');
$rm = new rating_manager();
$mform->addElement('header', 'modstandardratings', get_string('ratings', 'rating'));
$permission=CAP_ALLOW;
$rolenamestring = null;
$isupdate = false;
if (!empty($this->_cm)) {
$isupdate = true;
$context = context_module::instance($this->_cm->id);
$rolenames = get_role_names_with_caps_in_context($context, array('moodle/rating:rate', 'mod/'.$this->_cm->modname.':rate'));
$rolenamestring = implode(', ', $rolenames);
} else {
$rolenamestring = get_string('capabilitychecknotavailable','rating');
}
$mform->addElement('static', 'rolewarning', get_string('rolewarning','rating'), $rolenamestring);
$mform->addHelpButton('rolewarning', 'rolewarning', 'rating');
$mform->addElement('select', 'assessed', get_string('aggregatetype', 'rating') , $rm->get_aggregate_types());
$mform->setDefault('assessed', 0);
$mform->addHelpButton('assessed', 'aggregatetype', 'rating');
$gradeoptions = array('isupdate' => $isupdate,
'currentgrade' => false,
'hasgrades' => false,
'canrescale' => $this->_features->canrescale,
'useratings' => $this->_features->rating);
if ($isupdate) {
$gradeitem = grade_item::fetch(array('itemtype' => 'mod',
'itemmodule' => $this->_cm->modname,
'iteminstance' => $this->_cm->instance,
'itemnumber' => 0,
'courseid' => $COURSE->id));
if ($gradeitem) {
$gradeoptions['currentgrade'] = $gradeitem->grademax;
$gradeoptions['currentgradetype'] = $gradeitem->gradetype;
$gradeoptions['currentscaleid'] = $gradeitem->scaleid;
$gradeoptions['hasgrades'] = $gradeitem->has_grades();
}
}
$mform->addElement('modgrade', 'scale', get_string('scale'), $gradeoptions);
$mform->hideIf('scale', 'assessed', 'eq', 0);
$mform->addHelpButton('scale', 'modgrade', 'grades');
$mform->setDefault('scale', $CFG->gradepointdefault);
$mform->addElement('checkbox', 'ratingtime', get_string('ratingtime', 'rating'));
$mform->hideIf('ratingtime', 'assessed', 'eq', 0);
$mform->addElement('date_time_selector', 'assesstimestart', get_string('from'));
$mform->hideIf('assesstimestart', 'assessed', 'eq', 0);
$mform->disabledIf('assesstimestart', 'ratingtime');
$mform->addElement('date_time_selector', 'assesstimefinish', get_string('to'));
$mform->hideIf('assesstimefinish', 'assessed', 'eq', 0);
$mform->disabledIf('assesstimefinish', 'ratingtime');
$this->add_rating_settings($mform, 0);
}
//doing this here means splitting up the grade related settings on the lesson settings page
//$this->standard_grading_coursemodule_elements();
$mform->addElement('header', 'modstandardelshdr', get_string('modstandardels', 'form'));
$section = get_fast_modinfo($COURSE)->get_section_info($this->_section);
@ -737,6 +709,110 @@ abstract class moodleform_mod extends moodleform {
$this->plugin_extend_coursemodule_standard_elements();
}
/**
* Add rating settings.
*
* @param moodleform_mod $mform
* @param int $itemnumber
*/
protected function add_rating_settings($mform, int $itemnumber) {
global $CFG, $COURSE;
if ($this->gradedorrated && $this->gradedorrated !== 'rated') {
return;
}
$this->gradedorrated = 'rated';
require_once("{$CFG->dirroot}/rating/lib.php");
$rm = new rating_manager();
$component = "mod_{$this->_modname}";
$gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat');
$gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradepass');
$assessedfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'assessed');
$scalefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'scale');
$mform->addElement('header', 'modstandardratings', get_string('ratings', 'rating'));
$isupdate = !empty($this->_cm);
$rolenamestring = null;
if ($isupdate) {
$context = context_module::instance($this->_cm->id);
$capabilities = ['moodle/rating:rate', "mod/{$this->_cm->modname}:rate"];
$rolenames = get_role_names_with_caps_in_context($context, $capabilities);
$rolenamestring = implode(', ', $rolenames);
} else {
$rolenamestring = get_string('capabilitychecknotavailable', 'rating');
}
$mform->addElement('static', 'rolewarning', get_string('rolewarning', 'rating'), $rolenamestring);
$mform->addHelpButton('rolewarning', 'rolewarning', 'rating');
$mform->addElement('select', $assessedfieldname, get_string('aggregatetype', 'rating') , $rm->get_aggregate_types());
$mform->setDefault($assessedfieldname, 0);
$mform->addHelpButton($assessedfieldname, 'aggregatetype', 'rating');
$gradeoptions = [
'isupdate' => $isupdate,
'currentgrade' => false,
'hasgrades' => false,
'canrescale' => false,
'useratings' => true,
];
if ($isupdate) {
$gradeitem = grade_item::fetch([
'itemtype' => 'mod',
'itemmodule' => $this->_cm->modname,
'iteminstance' => $this->_cm->instance,
'itemnumber' => $itemnumber,
'courseid' => $COURSE->id,
]);
if ($gradeitem) {
$gradeoptions['currentgrade'] = $gradeitem->grademax;
$gradeoptions['currentgradetype'] = $gradeitem->gradetype;
$gradeoptions['currentscaleid'] = $gradeitem->scaleid;
$gradeoptions['hasgrades'] = $gradeitem->has_grades();
}
}
$mform->addElement('modgrade', $scalefieldname, get_string('scale'), $gradeoptions);
$mform->hideIf($scalefieldname, $assessedfieldname, 'eq', 0);
$mform->addHelpButton($scalefieldname, 'modgrade', 'grades');
$mform->setDefault($scalefieldname, $CFG->gradepointdefault);
$mform->addElement('checkbox', 'ratingtime', get_string('ratingtime', 'rating'));
$mform->hideIf('ratingtime', $assessedfieldname, 'eq', 0);
$mform->addElement('date_time_selector', 'assesstimestart', get_string('from'));
$mform->hideIf('assesstimestart', $assessedfieldname, 'eq', 0);
$mform->hideIf('assesstimestart', 'ratingtime');
$mform->addElement('date_time_selector', 'assesstimefinish', get_string('to'));
$mform->hideIf('assesstimefinish', $assessedfieldname, 'eq', 0);
$mform->hideIf('assesstimefinish', 'ratingtime');
if ($this->_features->gradecat) {
$mform->addElement(
'select',
$gradecatfieldname,
get_string('gradecategoryonmodform', 'grades'),
grade_get_categories_menu($COURSE->id, $this->_outcomesused)
);
$mform->addHelpButton($gradecatfieldname, 'gradecategoryonmodform', 'grades');
$mform->hideIf($gradecatfieldname, $assessedfieldname, 'eq', 0);
$mform->hideIf($gradecatfieldname, "{$scalefieldname}[modgrade_type]", 'eq', 'none');
}
// Grade to pass.
$mform->addElement('text', $gradepassfieldname, get_string('gradepass', 'grades'));
$mform->addHelpButton($gradepassfieldname, 'gradepass', 'grades');
$mform->setDefault($gradepassfieldname, '');
$mform->setType($gradepassfieldname, PARAM_RAW);
$mform->hideIf($gradepassfieldname, $assessedfieldname, 'eq', '0');
$mform->hideIf($gradepassfieldname, "{$scalefieldname}[modgrade_type]", 'eq', 'none');
}
/**
* Plugins can extend the coursemodule settings form.
*/
@ -811,6 +887,21 @@ abstract class moodleform_mod extends moodleform {
public function standard_grading_coursemodule_elements() {
global $COURSE, $CFG;
if ($this->gradedorrated && $this->gradedorrated !== 'graded') {
return;
}
if ($this->_features->rating) {
return;
}
$this->gradedorrated = 'graded';
$itemnumber = 0;
$component = "mod_{$this->_modname}";
$gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade');
$gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat');
$gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradepass');
$mform =& $this->_form;
$isupdate = !empty($this->_cm);
$gradeoptions = array('isupdate' => $isupdate,
@ -820,31 +911,27 @@ abstract class moodleform_mod extends moodleform {
'useratings' => $this->_features->rating);
if ($this->_features->hasgrades) {
if (!$this->_features->rating || $this->_features->gradecat) {
if ($this->_features->gradecat) {
$mform->addElement('header', 'modstandardgrade', get_string('grade'));
}
//if supports grades and grades arent being handled via ratings
if (!$this->_features->rating) {
if ($isupdate) {
$gradeitem = grade_item::fetch(array('itemtype' => 'mod',
'itemmodule' => $this->_cm->modname,
'iteminstance' => $this->_cm->instance,
'itemnumber' => 0,
'courseid' => $COURSE->id));
if ($gradeitem) {
$gradeoptions['currentgrade'] = $gradeitem->grademax;
$gradeoptions['currentgradetype'] = $gradeitem->gradetype;
$gradeoptions['currentscaleid'] = $gradeitem->scaleid;
$gradeoptions['hasgrades'] = $gradeitem->has_grades();
}
if ($isupdate) {
$gradeitem = grade_item::fetch(array('itemtype' => 'mod',
'itemmodule' => $this->_cm->modname,
'iteminstance' => $this->_cm->instance,
'itemnumber' => 0,
'courseid' => $COURSE->id));
if ($gradeitem) {
$gradeoptions['currentgrade'] = $gradeitem->grademax;
$gradeoptions['currentgradetype'] = $gradeitem->gradetype;
$gradeoptions['currentscaleid'] = $gradeitem->scaleid;
$gradeoptions['hasgrades'] = $gradeitem->has_grades();
}
$mform->addElement('modgrade', 'grade', get_string('grade'), $gradeoptions);
$mform->addHelpButton('grade', 'modgrade', 'grades');
$mform->setDefault('grade', $CFG->gradepointdefault);
}
$mform->addElement('modgrade', $gradefieldname, get_string('grade'), $gradeoptions);
$mform->addHelpButton($gradefieldname, 'modgrade', 'grades');
$mform->setDefault($gradefieldname, $CFG->gradepointdefault);
if ($this->_features->advancedgrading
and !empty($this->current->_advancedgradingdata['methods'])
@ -858,9 +945,7 @@ abstract class moodleform_mod extends moodleform {
$mform->addElement('select', 'advancedgradingmethod_'.$areaname,
get_string('gradingmethod', 'core_grading'), $this->current->_advancedgradingdata['methods']);
$mform->addHelpButton('advancedgradingmethod_'.$areaname, 'gradingmethod', 'core_grading');
if (!$this->_features->rating) {
$mform->hideIf('advancedgradingmethod_'.$areaname, 'grade[modgrade_type]', 'eq', 'none');
}
$mform->hideIf('advancedgradingmethod_'.$areaname, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
} else {
// the module defines multiple gradable areas, display a selector
@ -877,25 +962,19 @@ abstract class moodleform_mod extends moodleform {
}
if ($this->_features->gradecat) {
$mform->addElement('select', 'gradecat',
$mform->addElement('select', $gradecatfieldname,
get_string('gradecategoryonmodform', 'grades'),
grade_get_categories_menu($COURSE->id, $this->_outcomesused));
$mform->addHelpButton('gradecat', 'gradecategoryonmodform', 'grades');
if (!$this->_features->rating) {
$mform->hideIf('gradecat', 'grade[modgrade_type]', 'eq', 'none');
}
$mform->addHelpButton($gradecatfieldname, 'gradecategoryonmodform', 'grades');
$mform->hideIf($gradecatfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
}
// Grade to pass.
$mform->addElement('text', 'gradepass', get_string('gradepass', 'grades'));
$mform->addHelpButton('gradepass', 'gradepass', 'grades');
$mform->setDefault('gradepass', '');
$mform->setType('gradepass', PARAM_RAW);
if (!$this->_features->rating) {
$mform->hideIf('gradepass', 'grade[modgrade_type]', 'eq', 'none');
} else {
$mform->hideIf('gradepass', 'assessed', 'eq', '0');
}
$mform->addElement('text', $gradepassfieldname, get_string($gradepassfieldname, 'grades'));
$mform->addHelpButton($gradepassfieldname, $gradepassfieldname, 'grades');
$mform->setDefault($gradepassfieldname, '');
$mform->setType($gradepassfieldname, PARAM_RAW);
$mform->hideIf($gradepassfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none');
}
}
@ -1111,5 +1190,3 @@ abstract class moodleform_mod extends moodleform {
return $data;
}
}

View File

@ -54,6 +54,9 @@ class core_course_courselib_testcase extends advanced_testcase {
$moduleinfo->blockperiod = 60*60*24;
$moduleinfo->blockafter = 10;
$moduleinfo->warnafter = 5;
// Grading of whole forum settings.
$moduleinfo->grade_forum = 0;
}
/**
@ -395,6 +398,9 @@ class core_course_courselib_testcase extends advanced_testcase {
$moduleinfo->blockperiod = 60*60*24;
$moduleinfo->blockafter = 10;
$moduleinfo->warnafter = 5;
// Grading of whole forum settings.
$moduleinfo->grade_forum = 0;
}
/**

View File

@ -2065,7 +2065,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$outcomegradeitem->cmid = 0;
$outcomegradeitem->courseid = $course->id;
$outcomegradeitem->aggregationcoef = 0;
$outcomegradeitem->itemnumber = 1; // The activity's original grade item will be 0.
$outcomegradeitem->itemnumber = 1000; // Outcomes start at 1000.
$outcomegradeitem->gradetype = GRADE_TYPE_SCALE;
$outcomegradeitem->scaleid = $outcome->scaleid;
$outcomegradeitem->insert();
@ -2988,4 +2988,55 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$this->assertCount(1, $result);
$this->assertEquals($courses[0]->id, array_shift($result)->id);
}
/**
* Test get enrolled users by cmid function.
*/
public function test_get_enrolled_users_by_cmid() {
$this->resetAfterTest(true);
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
// Set the first created user to the test user.
self::setUser($user1);
// Create course to add the module.
$course1 = self::getDataGenerator()->create_course();
// Forum with tracking off.
$record = new stdClass();
$record->course = $course1->id;
$forum1 = self::getDataGenerator()->create_module('forum', $record);
// Following lines enrol and assign default role id to the users.
$this->getDataGenerator()->enrol_user($user1->id, $course1->id);
$this->getDataGenerator()->enrol_user($user2->id, $course1->id);
// Create what we expect to be returned when querying the course module.
$expectedusers = array(
'users' => array(),
'warnings' => array(),
);
$expectedusers['users'][0] = [
'id' => $user1->id,
'fullname' => fullname($user1),
'firstname' => $user1->firstname,
'lastname' => $user1->lastname,
];
$expectedusers['users'][1] = [
'id' => $user2->id,
'fullname' => fullname($user2),
'firstname' => $user2->firstname,
'lastname' => $user2->lastname,
];
// Test getting the users in a given context.
$users = core_course_external::get_enrolled_users_by_cmid($forum1->cmid);
$users = external_api::clean_returnvalue(core_course_external::get_enrolled_users_by_cmid_returns(), $users);
$this->assertEquals(2, count($users['users']));
$this->assertEquals($expectedusers, $users);
}
}

View File

@ -0,0 +1,2 @@
define ("core_grades/grades/grader/gradingpanel/normalise",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.failedUpdate=a.invalidResult=a.normaliseResult=void 0;a.normaliseResult=function normaliseResult(a){return{result:a,failed:!!a.warnings.length,success:!a.warnings.length,error:null}};a.invalidResult=function invalidResult(){return{success:!1,failed:!1,result:{},error:null}};a.failedUpdate=function failedUpdate(a){return{success:!1,failed:!0,result:{},error:a}}});
//# sourceMappingURL=normalise.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/normalise.js"],"names":["normaliseResult","result","failed","warnings","length","success","error","invalidResult","failedUpdate"],"mappings":"2NA8B+B,QAAlBA,CAAAA,eAAkB,CAAAC,CAAM,CAAI,CACrC,MAAO,CACHA,MAAM,CAANA,CADG,CAEHC,MAAM,CAAE,CAAC,CAACD,CAAM,CAACE,QAAP,CAAgBC,MAFvB,CAGHC,OAAO,CAAE,CAACJ,CAAM,CAACE,QAAP,CAAgBC,MAHvB,CAIHE,KAAK,CAAE,IAJJ,CAMV,C,iBAO4B,QAAhBC,CAAAA,aAAgB,EAAM,CAC/B,MAAO,CACHF,OAAO,GADJ,CAEHH,MAAM,GAFH,CAGHD,MAAM,CAAE,EAHL,CAIHK,KAAK,CAAE,IAJJ,CAMV,C,gBAQ2B,QAAfE,CAAAA,YAAe,CAAAF,CAAK,CAAI,CACjC,MAAO,CACHD,OAAO,GADJ,CAEHH,MAAM,GAFH,CAGHD,MAAM,CAAE,EAHL,CAIHK,KAAK,CAALA,CAJG,CAMV,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 <http://www.gnu.org/licenses/>.\n\n/**\n * Repository for simple direct grading panel.\n *\n * @module core_grades/grades/grader/gradingpanel/repository\n * @package core_grades\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Normalise a resultset for consumption by the grader.\n *\n * @param {Object} result The result returned from a grading web service\n * @return {Object}\n */\nexport const normaliseResult = result => {\n return {\n result,\n failed: !!result.warnings.length,\n success: !result.warnings.length,\n error: null,\n };\n};\n\n/**\n * Return the resultset used to describe an invalid result.\n *\n * @return {Object}\n */\nexport const invalidResult = () => {\n return {\n success: false,\n failed: false,\n result: {},\n error: null,\n };\n};\n\n/**\n * Return the resultset used to describe a failed update.\n *\n * @param {Object} error\n * @return {Object}\n */\nexport const failedUpdate = error => {\n return {\n success: false,\n failed: true,\n result: {},\n error,\n };\n};\n"],"file":"normalise.min.js"}

View File

@ -0,0 +1,2 @@
define ("core_grades/grades/grader/gradingpanel/point",["exports","./repository","jquery","./normalise"],function(a,b,c,d){"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);function e(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function f(a){return function(){var b=this,c=arguments;return new Promise(function(d,f){var i=a.apply(b,c);function g(a){e(i,d,f,g,h,"next",a)}function h(a){e(i,d,f,g,h,"throw",a)}g(void 0)})}}a.fetchCurrentGrade=function fetchCurrentGrade(){return(0,b.fetchGrade)("point").apply(void 0,arguments)};var g=function(){var a=f(regeneratorRuntime.mark(function a(e,f,g,h,i){var j,k;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:j=i.querySelector("form");k=j.querySelector("input[name=\"grade\"]");if(!(!k.checkValidity()||!k.value.trim())){a.next=4;break}return a.abrupt("return",d.invalidResult);case 4:a.next=6;return(0,b.saveGrade)("point")(e,f,g,h,(0,c.default)(j).serialize());case 6:return a.abrupt("return",a.sent);case 7:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=g});
//# sourceMappingURL=point.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/point.js"],"names":["fetchCurrentGrade","storeCurrentGrade","component","context","itemname","userId","rootNode","form","querySelector","grade","checkValidity","value","trim","invalidResult","serialize"],"mappings":"wOA0BA,uD,mVAaiC,QAApBA,CAAAA,iBAAoB,SAAa,iBAAW,OAAX,yBAAb,C,CAY1B,GAAMC,CAAAA,CAAiB,4CAAG,WAAMC,CAAN,CAAiBC,CAAjB,CAA0BC,CAA1B,CAAoCC,CAApC,CAA4CC,CAA5C,2FACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,CAEvBC,CAFuB,CAEfF,CAAI,CAACC,aAAL,CAAmB,uBAAnB,CAFe,MAIzB,CAACC,CAAK,CAACC,aAAN,EAAD,EAA0B,CAACD,CAAK,CAACE,KAAN,CAAYC,IAAZ,EAJF,2CAKlBC,eALkB,wBAQhB,gBAAU,OAAV,EAAmBX,CAAnB,CAA8BC,CAA9B,CAAuCC,CAAvC,CAAiDC,CAAjD,CAAyD,cAAOE,CAAP,EAAaO,SAAb,EAAzD,CARgB,iFAAH,uDAAvB,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 <http://www.gnu.org/licenses/>.\n\n/**\n * Grading panel for simple direct grading.\n *\n * @module core_grades/grades/grader/gradingpanel/point\n * @package core_grades\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {saveGrade, fetchGrade} from './repository';\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\nimport {invalidResult} from './normalise';\n\n/**\n * Fetch the current grade for a user.\n *\n * @param {String} component\n * @param {Number} context\n * @param {String} itemname\n * @param {Number} userId\n * @param {Element} rootNode\n * @return {Object}\n */\nexport const fetchCurrentGrade = (...args) => fetchGrade('point')(...args);\n\n/**\n * Store a new grade for a user.\n *\n * @param {String} component\n * @param {Number} context\n * @param {String} itemname\n * @param {Number} userId\n * @param {Element} rootNode\n * @return {Object}\n */\nexport const storeCurrentGrade = async(component, context, itemname, userId, rootNode) => {\n const form = rootNode.querySelector('form');\n const grade = form.querySelector('input[name=\"grade\"]');\n\n if (!grade.checkValidity() || !grade.value.trim()) {\n return invalidResult;\n }\n\n return await saveGrade('point')(component, context, itemname, userId, jQuery(form).serialize());\n};\n"],"file":"point.min.js"}

View File

@ -0,0 +1,2 @@
define ("core_grades/grades/grader/gradingpanel/repository",["exports","core/ajax","./normalise"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.saveGrade=a.fetchGrade=void 0;function d(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function e(a){return function(){var b=this,c=arguments;return new Promise(function(e,f){var i=a.apply(b,c);function g(a){d(i,e,f,g,h,"next",a)}function h(a){d(i,e,f,g,h,"throw",a)}g(void 0)})}}a.fetchGrade=function fetchGrade(a){return function(c,d,e,f){return(0,b.call)([{methodname:"core_grades_grader_gradingpanel_".concat(a,"_fetch"),args:{component:c,contextid:d,itemname:e,gradeduserid:f}}])[0]}};a.saveGrade=function saveGrade(a){return function(){var d=e(regeneratorRuntime.mark(function d(e,f,g,h,i){return regeneratorRuntime.wrap(function(d){while(1){switch(d.prev=d.next){case 0:d.t0=c.normaliseResult;d.next=3;return(0,b.call)([{methodname:"core_grades_grader_gradingpanel_".concat(a,"_store"),args:{component:e,contextid:f,itemname:g,gradeduserid:h,formdata:i}}])[0];case 3:d.t1=d.sent;return d.abrupt("return",(0,d.t0)(d.t1));case 5:case"end":return d.stop();}}},d)}));return function(){return d.apply(this,arguments)}}()}});
//# sourceMappingURL=repository.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/repository.js"],"names":["fetchGrade","type","component","contextid","itemname","gradeduserid","methodname","args","saveGrade","formdata","normaliseResult"],"mappings":"4hBA0B0B,QAAbA,CAAAA,UAAa,CAAAC,CAAI,QAAI,UAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAChF,MAAO,WAAU,CAAC,CACdC,UAAU,2CAAqCL,CAArC,UADI,CAEdM,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAFQ,CAAD,CAAV,EAQH,CARG,CASV,CAV6B,C,aAYL,QAAZG,CAAAA,SAAY,CAAAP,CAAI,oDAAI,WAAMC,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,wFACtBC,iBADsB,gBACA,WAAU,CAAC,CACpCJ,UAAU,2CAAqCL,CAArC,UAD0B,CAEpCM,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFI,QAAQ,CAARA,CALE,CAF8B,CAAD,CAAV,EASzB,CATyB,CADA,qGAAJ,wD","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Repository for simple direct grading panel.\n *\n * @module core_grades/grades/grader/gradingpanel/repository\n * @package core_grades\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from './normalise';\n\nexport const fetchGrade = type => (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `core_grades_grader_gradingpanel_${type}_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\nexport const saveGrade = type => async(component, contextid, itemname, gradeduserid, formdata) => {\n return normaliseResult(await fetchMany([{\n methodname: `core_grades_grader_gradingpanel_${type}_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n formdata,\n },\n }])[0]);\n};\n"],"file":"repository.min.js"}

View File

@ -0,0 +1,2 @@
define ("core_grades/grades/grader/gradingpanel/scale",["exports","./repository","jquery","./normalise"],function(a,b,c,d){"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(){return(0,b.fetchGrade)("scale").apply(void 0,arguments)};var e=function(a,e,f,g,h){var i=h.querySelector("form"),j=i.querySelector("select[name=\"grade\"]");if(!j.checkValidity()||!j.value.trim()){return d.invalidResult}return(0,b.saveGrade)("scale")(a,e,f,g,(0,c.default)(i).serialize())};a.storeCurrentGrade=e});
//# sourceMappingURL=scale.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/scale.js"],"names":["fetchCurrentGrade","storeCurrentGrade","component","context","itemname","userId","rootNode","form","querySelector","grade","checkValidity","value","trim","invalidResult","serialize"],"mappings":"wOA0BA,uD,oBAGiC,QAApBA,CAAAA,iBAAoB,SAAa,iBAAW,OAAX,yBAAb,C,CAE1B,GAAMC,CAAAA,CAAiB,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAAqBC,CAArB,CAA+BC,CAA/B,CAAuCC,CAAvC,CAAoD,IAC3EC,CAAAA,CAAI,CAAGD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADoE,CAE3EC,CAAK,CAAGF,CAAI,CAACC,aAAL,CAAmB,wBAAnB,CAFmE,CAIjF,GAAI,CAACC,CAAK,CAACC,aAAN,EAAD,EAA0B,CAACD,CAAK,CAACE,KAAN,CAAYC,IAAZ,EAA/B,CAAmD,CAC/C,MAAOC,gBACV,CAED,MAAO,gBAAU,OAAV,EAAmBX,CAAnB,CAA8BC,CAA9B,CAAuCC,CAAvC,CAAiDC,CAAjD,CAAyD,cAAOE,CAAP,EAAaO,SAAb,EAAzD,CACV,CATM,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 <http://www.gnu.org/licenses/>.\n\n/**\n * Grading panel for simple direct grading.\n *\n * @module core_grades/grades/grader/gradingpanel/scale\n * @package core_grades\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {saveGrade, fetchGrade} from './repository';\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\nimport {invalidResult} from './normalise';\n\nexport const fetchCurrentGrade = (...args) => fetchGrade('scale')(...args);\n\nexport const storeCurrentGrade = (component, context, itemname, userId, rootNode) => {\n const form = rootNode.querySelector('form');\n const grade = form.querySelector('select[name=\"grade\"]');\n\n if (!grade.checkValidity() || !grade.value.trim()) {\n return invalidResult;\n }\n\n return saveGrade('scale')(component, context, itemname, userId, jQuery(form).serialize());\n};\n"],"file":"scale.min.js"}

View File

@ -0,0 +1,67 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Repository for simple direct grading panel.
*
* @module core_grades/grades/grader/gradingpanel/repository
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Normalise a resultset for consumption by the grader.
*
* @param {Object} result The result returned from a grading web service
* @return {Object}
*/
export const normaliseResult = result => {
return {
result,
failed: !!result.warnings.length,
success: !result.warnings.length,
error: null,
};
};
/**
* Return the resultset used to describe an invalid result.
*
* @return {Object}
*/
export const invalidResult = () => {
return {
success: false,
failed: false,
result: {},
error: null,
};
};
/**
* Return the resultset used to describe a failed update.
*
* @param {Object} error
* @return {Object}
*/
export const failedUpdate = error => {
return {
success: false,
failed: true,
result: {},
error,
};
};

View File

@ -0,0 +1,61 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Grading panel for simple direct grading.
*
* @module core_grades/grades/grader/gradingpanel/point
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {saveGrade, fetchGrade} from './repository';
// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()
import jQuery from 'jquery';
import {invalidResult} from './normalise';
/**
* Fetch the current grade for a user.
*
* @param {String} component
* @param {Number} context
* @param {String} itemname
* @param {Number} userId
* @param {Element} rootNode
* @return {Object}
*/
export const fetchCurrentGrade = (...args) => fetchGrade('point')(...args);
/**
* Store a new grade for a user.
*
* @param {String} component
* @param {Number} context
* @param {String} itemname
* @param {Number} userId
* @param {Element} rootNode
* @return {Object}
*/
export const storeCurrentGrade = async(component, context, itemname, userId, rootNode) => {
const form = rootNode.querySelector('form');
const grade = form.querySelector('input[name="grade"]');
if (!grade.checkValidity() || !grade.value.trim()) {
return invalidResult;
}
return await saveGrade('point')(component, context, itemname, userId, jQuery(form).serialize());
};

View File

@ -0,0 +1,50 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Repository for simple direct grading panel.
*
* @module core_grades/grades/grader/gradingpanel/repository
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
import {normaliseResult} from './normalise';
export const fetchGrade = type => (component, contextid, itemname, gradeduserid) => {
return fetchMany([{
methodname: `core_grades_grader_gradingpanel_${type}_fetch`,
args: {
component,
contextid,
itemname,
gradeduserid,
},
}])[0];
};
export const saveGrade = type => async(component, contextid, itemname, gradeduserid, formdata) => {
return normaliseResult(await fetchMany([{
methodname: `core_grades_grader_gradingpanel_${type}_store`,
args: {
component,
contextid,
itemname,
gradeduserid,
formdata,
},
}])[0]);
};

View File

@ -0,0 +1,41 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Grading panel for simple direct grading.
*
* @module core_grades/grades/grader/gradingpanel/scale
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {saveGrade, fetchGrade} from './repository';
// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()
import jQuery from 'jquery';
import {invalidResult} from './normalise';
export const fetchCurrentGrade = (...args) => fetchGrade('scale')(...args);
export const storeCurrentGrade = (component, context, itemname, userId, rootNode) => {
const form = rootNode.querySelector('form');
const grade = form.querySelector('select[name="grade"]');
if (!grade.checkValidity() || !grade.value.trim()) {
return invalidResult;
}
return saveGrade('scale')(component, context, itemname, userId, jQuery(form).serialize());
};

View File

@ -0,0 +1,477 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Compontent definition of a gradeitem.
*
* @package core_grades
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_grades;
use context;
use gradingform_controller;
use gradingform_instance;
use moodle_exception;
use stdClass;
use grade_item as core_gradeitem;
use grading_manager;
/**
* Compontent definition of a gradeitem.
*
* @package core_grades
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class component_gradeitem {
/** @var array The scale data for the current grade item */
protected $scale;
/** @var string The component */
protected $component;
/** @var context The context for this activity */
protected $context;
/** @var string The item name */
protected $itemname;
/** @var int The grade itemnumber */
protected $itemnumber;
/**
* component_gradeitem constructor.
*
* @param string $component
* @param context $context
* @param string $itemname
* @throws \coding_exception
*/
final protected function __construct(string $component, context $context, string $itemname) {
$this->component = $component;
$this->context = $context;
$this->itemname = $itemname;
$this->itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
}
/**
* Fetch an instance of a specific component_gradeitem.
*
* @param string $component
* @param context $context
* @param string $itemname
* @return self
*/
public static function instance(string $component, context $context, string $itemname): self {
$itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
$classname = "{$component}\\grades\\{$itemname}_gradeitem";
if (!class_exists($classname)) {
throw new coding_exception("Unknown gradeitem {$itemname} for component {$classname}");
}
return $classname::load_from_context($context);
}
/**
* Load an instance of the current component_gradeitem based on context.
*
* @param context $context
* @return self
*/
abstract public static function load_from_context(context $context): self;
/**
* The table name used for grading.
*
* @return string
*/
abstract protected function get_table_name(): string;
/**
* Get the itemid for the current gradeitem.
*
* @return int
*/
public function get_grade_itemid(): int {
return component_gradeitems::get_itemnumber_from_itemname($this->component, $this->itemname);
}
/**
* Whether grading is enabled for this item.
*
* @return bool
*/
abstract public function is_grading_enabled(): bool;
/**
* Get the grade value for this instance.
* The itemname is translated to the relevant grade field for the activity.
*
* @return int
*/
abstract protected function get_gradeitem_value(): ?int;
/**
* Whether the grader can grade the gradee.
*
* @param stdClass $gradeduser The user being graded
* @param stdClass $grader The user who is grading
* @return bool
*/
abstract public function user_can_grade(stdClass $gradeduser, stdClass $grader): bool;
/**
* Require that the user can grade, throwing an exception if not.
*
* @param stdClass $gradeduser The user being graded
* @param stdClass $grader The user who is grading
* @throws required_capability_exception
*/
abstract public function require_user_can_grade(stdClass $gradeduser, stdClass $grader): void;
/**
* Get the scale if a scale is being used.
*
* @return stdClass
*/
protected function get_scale(): ?stdClass {
global $DB;
$gradetype = $this->get_gradeitem_value();
if ($gradetype > 0) {
return null;
}
// This is a scale.
if (null === $this->scale) {
$this->scale = $DB->get_record('scale', ['id' => -1 * $gradetype]);
}
return $this->scale;
}
/**
* Check whether a scale is being used for this grade item.
*
* @return bool
*/
public function is_using_scale(): bool {
$gradetype = $this->get_gradeitem_value();
return $gradetype < 0;
}
/**
* Whether this grade item is configured to use direct grading.
*
* @return bool
*/
public function is_using_direct_grading(): bool {
if ($this->is_using_scale()) {
return false;
}
if ($this->get_advanced_grading_controller()) {
return false;
}
return true;
}
/**
* Whether this grade item is configured to use advanced grading.
*
* @return bool
*/
public function is_using_advanced_grading(): bool {
if ($this->is_using_scale()) {
return false;
}
if ($this->get_advanced_grading_controller()) {
return true;
}
return false;
}
/**
* Get the name of the advanced grading method.
*
* @return string
*/
public function get_advanced_grading_method(): ?string {
$gradingmanager = $this->get_grading_manager();
if (empty($gradingmanager)) {
return null;
}
return $gradingmanager->get_active_method();
}
/**
* Get the name of the component responsible for grading this gradeitem.
*
* @return string
*/
public function get_grading_component_name(): ?string {
if (!$this->is_grading_enabled()) {
return null;
}
if ($method = $this->get_advanced_grading_method()) {
return "gradingform_{$method}";
}
return 'core_grades';
}
/**
* Get the name of the component subtype responsible for grading this gradeitem.
*
* @return string
*/
public function get_grading_component_subtype(): ?string {
if (!$this->is_grading_enabled()) {
return null;
}
if ($method = $this->get_advanced_grading_method()) {
return null;
}
if ($this->is_using_scale()) {
return 'scale';
}
return 'point';
}
/**
* Whether decimals are allowed.
*
* @return bool
*/
protected function allow_decimals(): bool {
return $this->get_gradeitem_value() > 0;
}
/**
* Get the grading manager for this advanced grading definition.
*
* @return grading_manager
*/
protected function get_grading_manager(): ?grading_manager {
require_once(__DIR__ . '/../grading/lib.php');
return get_grading_manager($this->context, $this->component, $this->itemname);
}
/**
* Get the advanced grading controller if advanced grading is enabled.
*
* @return gradingform_controller
*/
protected function get_advanced_grading_controller(): ?gradingform_controller {
$gradingmanager = $this->get_grading_manager();
if (empty($gradingmanager)) {
return null;
}
if ($gradingmethod = $gradingmanager->get_active_method()) {
return $gradingmanager->get_controller($gradingmethod);
}
return null;
}
/**
* Get the list of available grade items.
*
* @return array
*/
public function get_grade_menu(): array {
return make_grades_menu($this->get_gradeitem_value());
}
/**
* Check whether the supplied grade is valid and throw an exception if not.
*
* @param float $grade The value being checked
* @throws moodle_exception
* @return bool
*/
public function check_grade_validity(?float $grade): bool {
$grade = grade_floatval(unformat_float($grade));
if ($grade) {
if ($this->is_using_scale()) {
// Fetch all options for this scale.
$scaleoptions = make_menu_from_list($this->get_scale()->scale);
if ($grade != -1 && !array_key_exists((int) $grade, $scaleoptions)) {
// The selected option did not exist.
throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
'maxgrade' => count($scaleoptions),
'grade' => $grade,
]);
}
} else if ($grade) {
$maxgrade = $this->get_gradeitem_value();
if ($grade > $maxgrade) {
// The grade is greater than the maximum possible value.
throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
'maxgrade' => $maxgrade,
'grade' => $grade,
]);
} else if ($grade < 0) {
// Negative grades are not supported.
throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
'maxgrade' => $maxgrade,
'grade' => $grade,
]);
}
}
}
return true;
}
/**
* Create an empty row in the grade for the specified user and grader.
*
* @param stdClass $gradeduser The user being graded
* @param stdClass $grader The user who is grading
* @return stdClass The newly created grade record
*/
abstract public function create_empty_grade(stdClass $gradeduser, stdClass $grader): stdClass;
/**
* Get the grade record for the specified grade id.
*
* @param int $gradeid
* @return stdClass
* @throws \dml_exception
*/
public function get_grade(int $gradeid): stdClass {
global $DB;
$grade = $DB->get_record($this->get_table_name(), ['id' => $gradeid]);
return $grade ?: null;
}
/**
* Get the grade for the specified user.
*
* @param stdClass $gradeduser The user being graded
* @param stdClass $grader The user who is grading
* @return stdClass The grade value
*/
abstract public function get_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass;
/**
* Get grades for all users for the specified gradeitem.
*
* @return stdClass[] The grades
*/
abstract public function get_all_grades(): array;
/**
* Create or update the grade.
*
* @param stdClass $grade
* @return bool Success
*/
abstract protected function store_grade(stdClass $grade): bool;
/**
* Create or update the grade.
*
* @param stdClass $gradeduser The user being graded
* @param stdClass $grader The user who is grading
* @param stdClass $formdata The data submitted
* @return bool Success
*/
public function store_grade_from_formdata(stdClass $gradeduser, stdClass $grader, stdClass $formdata): bool {
// Require gradelib for grade_floatval.
require_once(__DIR__ . '/../../lib/gradelib.php');
$grade = $this->get_grade_for_user($gradeduser, $grader);
if ($this->is_using_advanced_grading()) {
$instanceid = $formdata->instanceid;
$gradinginstance = $this->get_advanced_grading_instance($grader, $grade, (int) $instanceid);
$grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id);
if ($grade->grade == -1) {
// In advanced grading, a value of -1 means no data.
return false;
}
} else {
// Handle the case when grade is set to No Grade.
if (isset($formdata->grade)) {
$grade->grade = grade_floatval(unformat_float($formdata->grade));
}
}
return $this->store_grade($grade);
}
/**
* Get the advanced grading instance for the specified grade entry.
*
* @param stdClass $grader The user who is grading
* @param stdClass $grade The row from the grade table.
* @param int $instanceid The instanceid of the advanced grading form
* @return gradingform_instance
*/
public function get_advanced_grading_instance(stdClass $grader, stdClass $grade, int $instanceid = null): ?gradingform_instance {
$controller = $this->get_advanced_grading_controller($this->itemname);
if (empty($controller)) {
// Advanced grading not enabeld for this item.
return null;
}
if (!$controller->is_form_available()) {
// The form is not available for this item.
return null;
}
// Fetch the instance for the specified graderid/itemid.
$gradinginstance = $controller->fetch_instance(
(int) $grader->id,
(int) $grade->id,
$instanceid
);
// Set the allowed grade range.
$gradinginstance->get_controller()->set_grade_range(
$this->get_grade_menu(),
$this->allow_decimals()
);
return $gradinginstance;
}
}

View File

@ -0,0 +1,219 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Helper class to fetch information about component grade items.
*
* @package core_grades
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_grades;
use code_grades\local\gradeitem\itemnumber_mapping;
use code_grades\local\gradeitem\advancedgrading_mapping;
/**
* Helper class to fetch information about component grade items.
*
* @package core_grades
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class component_gradeitems {
/**
* Get the gradeitems classname for the specific component.
*
* @param string $component The component to fetch the classname for
* @return string The composed classname
*/
protected static function get_component_classname(string $component): string {
return "{$component}\\grades\gradeitems";
}
/**
* Get the grade itemnumber mapping for a component.
*
* @param string $component The component that the grade item belongs to
* @return array
*/
public static function get_itemname_mapping_for_component(string $component): array {
$classname = "{$component}\\grades\gradeitems";
if (!class_exists($classname)) {
return [
0 => '',
];
}
if (!is_subclass_of($classname, 'core_grades\local\gradeitem\itemnumber_mapping')) {
throw new \coding_exception("The {$classname} class does not implement " . itemnumber_mapping::class);
}
return $classname::get_itemname_mapping_for_component();
}
/**
* Whether the named grading item exists.
*
* @param string $component
* @param string $itemname
* @return bool
*/
public static function is_valid_itemname(string $component, string $itemname): bool {
$items = self::get_itemname_mapping_for_component($component);
return array_search($itemname, $items) !== false;
}
/**
* Check whether the component class defines the advanced grading items.
*
* @param string $component The component to check
* @return bool
*/
public static function defines_advancedgrading_itemnames_for_component(string $component): bool {
return is_subclass_of(self::get_component_classname($component), 'core_grades\local\gradeitem\advancedgrading_mapping');
}
/**
* Get the list of advanced grading item names for the named component.
*
* @param string $component
* @return array
*/
public static function get_advancedgrading_itemnames_for_component(string $component): array {
$classname = self::get_component_classname($component);
if (!self::defines_advancedgrading_itemnames_for_component($component)) {
throw new \coding_exception("The {$classname} class does not implement " . advancedgrading_mapping::class);
}
return $classname::get_advancedgrading_itemnames();
}
/**
* Whether the named grading item name supports advanced grading.
*
* @param string $component
* @param string $itemname
* @return bool
*/
public static function is_advancedgrading_itemname(string $component, string $itemname): bool {
$gradingareas = self::get_advancedgrading_itemnames_for_component($component);
return array_search($itemname, $gradingareas) !== false;
}
/**
* Get the suffixed field name for an activity field mapped from its itemnumber.
*
* For legacy reasons, the first itemnumber has no suffix on field names.
*
* @param string $component The component that the grade item belongs to
* @param int $itemnumber The grade itemnumber
* @param string $fieldname The name of the field to be rewritten
* @return string The translated field name
*/
public static function get_field_name_for_itemnumber(string $component, int $itemnumber, string $fieldname): string {
$itemname = static::get_itemname_from_itemnumber($component, $itemnumber);
if ($itemname) {
return "{$fieldname}_{$itemname}";
}
return $fieldname;
}
/**
* Get the suffixed field name for an activity field mapped from its itemnumber.
*
* For legacy reasons, the first itemnumber has no suffix on field names.
*
* @param string $component The component that the grade item belongs to
* @param string $itemname The grade itemname
* @param string $fieldname The name of the field to be rewritten
* @return string The translated field name
*/
public static function get_field_name_for_itemname(string $component, string $itemname, string $fieldname): string {
if (empty($itemname)) {
return $fieldname;
}
$itemnumber = static::get_itemnumber_from_itemname($component, $itemname);
if ($itemnumber > 0) {
return "{$fieldname}_{$itemname}";
}
return $fieldname;
}
/**
* Get the itemname for an itemnumber.
*
* For legacy compatability when the itemnumber is 0, the itemname will always be empty.
*
* @param string $component The component that the grade item belongs to
* @param int $itemnumber The grade itemnumber
* @return int The grade itemnumber of the itemname
*/
public static function get_itemname_from_itemnumber(string $component, int $itemnumber): string {
if ($itemnumber === 0) {
return '';
}
$mappings = self::get_itemname_mapping_for_component($component);
if (isset($mappings[$itemnumber])) {
return $mappings[$itemnumber];
}
if ($itemnumber >= 1000) {
// An itemnumber >= 1000 belongs to an outcome.
return '';
}
throw new \coding_exception("Unknown itemnumber mapping for {$itemnumber} in {$component}");
}
/**
* Get the itemnumber for a item name.
*
* For legacy compatability when the itemname is empty, the itemnumber will always be 0.
*
* @param string $component The component that the grade item belongs to
* @param string $itemname The grade itemname
* @return int The grade itemname of the itemnumber
*/
public static function get_itemnumber_from_itemname(string $component, string $itemname): int {
if (empty($itemname)) {
return 0;
}
$mappings = self::get_itemname_mapping_for_component($component);
$flipped = array_flip($mappings);
if (isset($flipped[$itemname])) {
return $flipped[$itemname];
}
throw new \coding_exception("Unknown itemnumber mapping for {$itemname} in {$component}");
}
}

View File

@ -0,0 +1,172 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Web service functions relating to point grades and grading.
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_grades\grades\grader\gradingpanel\point\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_function_parameters;
use external_multiple_structure;
use external_single_structure;
use external_value;
use external_warnings;
use moodle_exception;
use required_capability_exception;
use stdClass;
/**
* External grading panel point API
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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
* @throws \dml_exception
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
* @throws coding_exception
* @throws moodle_exception
* @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 (!$gradeitem->is_using_direct_grading()) {
throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for direct grading");
}
// Fetch the actual data.
$gradeduser = \core_user::get_user($gradeduserid);
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
return self::get_fetch_data($grade);
}
/**
* Get the data to be fetched.
*
* @param stdClass $grade
* @return array
*/
public static function get_fetch_data(stdClass $grade): array {
return [
'templatename' => 'core_grades/grades/grader/gradingpanel/point',
'grade' => [
'grade' => $grade->grade,
'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([
'grade' => new external_value(PARAM_FLOAT, 'The numeric grade'),
'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(),
]);
}
}

View File

@ -0,0 +1,170 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Web service functions relating to point grades and grading.
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_grades\grades\grader\gradingpanel\point\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_function_parameters;
use external_multiple_structure;
use external_single_structure;
use external_value;
use external_warnings;
use moodle_exception;
use required_capability_exception;
/**
* External grading panel point API
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store 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
),
'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
* @param string $formdata
* @return array
* @throws \dml_exception
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
* @throws coding_exception
* @throws moodle_exception
* @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 (!$gradeitem->is_using_direct_grading()) {
throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for direct grading");
}
// Parse the serialised string into an object.
$data = [];
parse_str($formdata, $data);
// Grade.
$gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data);
// Fetch the updated grade back out.
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
return fetch::get_fetch_data($grade);
}
/**
* 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();
}
}

View File

@ -0,0 +1,192 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Web service functions relating to scale grades and grading.
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_grades\grades\grader\gradingpanel\scale\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_multiple_structure;
use external_single_structure;
use external_value;
use external_warnings;
use moodle_exception;
use required_capability_exception;
use stdClass;
/**
* External grading panel scale API
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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
* @throws \dml_exception
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
* @throws coding_exception
* @throws moodle_exception
* @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 (!$gradeitem->is_using_scale()) {
throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for grading with scales");
}
$gradeduser = \core_user::get_user($gradeduserid);
return self::get_fetch_data($gradeitem, $gradeduser);
}
/**
* Get the data to be fetched.
*
* @param gradeitem $gradeitem
* @param stdClass $gradeduser
* @return array
*/
public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser): array {
global $USER;
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
$currentgrade = (int) unformat_float($grade->grade);
$menu = $gradeitem->get_grade_menu();
$values = array_map(function($description, $value) use ($currentgrade) {
return [
'value' => $value,
'title' => $description,
'selected' => ($value == $currentgrade),
];
}, $menu, array_keys($menu));
return [
'templatename' => 'core_grades/grades/grader/gradingpanel/scale',
'grade' => [
'options' => $values,
'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([
'options' => new external_multiple_structure(
new external_single_structure([
'value' => new external_value(PARAM_FLOAT, 'The grade value'),
'title' => new external_value(PARAM_RAW, 'The description fo the option'),
'selected' => new external_value(PARAM_BOOL, 'Whether this item is currently selected'),
]),
'The description of the grade option'
),
'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(),
]);
}
}

View File

@ -0,0 +1,167 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Web service functions relating to scale grades and grading.
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_grades\grades\grader\gradingpanel\scale\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_function_parameters;
use external_multiple_structure;
use external_single_structure;
use external_value;
use external_warnings;
use moodle_exception;
use required_capability_exception;
/**
* External grading panel scale API
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store 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
),
'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
* @param string $formdata
* @return array
* @throws \dml_exception
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
* @throws coding_exception
* @throws moodle_exception
* @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 (!$gradeitem->is_using_scale()) {
throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for grading with scales");
}
// 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();
}
}

View File

@ -0,0 +1,43 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Grade item, itemnumber mapping.
*
* @package core_grades
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_grades\local\gradeitem;
/**
* Grade item, itemnumber mapping.
*
* @package core_grades
* @copyright Andrew Nicols <andrew@nicols.co.uk>
*/
interface advancedgrading_mapping {
/**
* Get the list of advanced grading item names for this component.
*
* @return array
*/
public static function get_advancedgrading_itemnames(): array;
}

View File

@ -0,0 +1,43 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Grade item, itemnumber mapping.
*
* @package core_grades
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace core_grades\local\gradeitem;
/**
* Grade item, itemnumber mapping.
*
* @package core_grades
* @copyright Andrew Nicols <andrew@nicols.co.uk>
*/
interface itemnumber_mapping {
/**
* Get the grade item mapping of item number to item name.
*
* @return array
*/
public static function get_itemname_mapping_for_component(): array;
}

View File

@ -0,0 +1,2 @@
define ("gradingform_guide/grades/grader/gradingpanel",["exports","core/ajax","core_grades/grades/grader/gradingpanel/normalise","jquery"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;d=function(a){return a&&a.__esModule?a:{default:a}}(d);function e(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function f(a){return function(){var b=this,c=arguments;return new Promise(function(d,f){var i=a.apply(b,c);function g(a){e(i,d,f,g,h,"next",a)}function h(a){e(i,d,f,g,h,"throw",a)}g(void 0)})}}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 g=function(){var a=f(regeneratorRuntime.mark(function a(e,f,g,h,i){var j;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:j=i.querySelector("form");a.t0=c.normaliseResult;a.next=4;return(0,b.call)([{methodname:"gradingform_guide_grader_gradingpanel_store",args:{component:e,contextid:f,itemname:g,gradeduserid:h,formdata:(0,d.default)(j).serialize()}}])[0];case 4:a.t1=a.sent;return a.abrupt("return",(0,a.t0)(a.t1));case 6:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=g});
//# sourceMappingURL=gradingpanel.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/grades/grader/gradingpanel.js"],"names":["fetchCurrentGrade","component","contextid","itemname","gradeduserid","methodname","args","storeCurrentGrade","rootNode","form","querySelector","normaliseResult","formdata","serialize"],"mappings":"0QA4BA,uD,mVAYiC,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,CAaM,GAAMG,CAAAA,CAAiB,4CAAG,WAAMN,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,yFACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,MAGtBC,iBAHsB,gBAGA,WAAU,CAAC,CACpCN,UAAU,8CAD0B,CAEpCC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFQ,QAAQ,CAAE,cAAOH,CAAP,EAAaI,SAAb,EALR,CAF8B,CAAD,CAAV,EASzB,CATyB,CAHA,qGAAH,uDAAvB,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 <http://www.gnu.org/licenses/>.\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 <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';\n\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n *\n * @returns {Promise}\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/**\n * For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n * @param {HTMLElement} rootNode\n *\n * @returns {Promise}\n */\nexport const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, rootNode) => {\n const form = rootNode.querySelector('form');\n\n return normaliseResult(await 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"}

View File

@ -0,0 +1,2 @@
define ("gradingform_guide/grades/grader/gradingpanel/comments",["exports","./comments/selectors"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);a.init=function init(a){var c=document.querySelector("#".concat(a));c.addEventListener("click",function(a){if(!a.target.matches(b.default.frequentComment)){return}a.preventDefault();var c=a.target.closest(b.default.frequentComment),d=c.closest(b.default.criterion),e=d.querySelector(b.default.remark);if(!e){return}if(e.value.trim()){e.value+="\n".concat(c.innerHTML)}else{e.value+=c.innerHTML}})}});
//# sourceMappingURL=comments.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/comments.js"],"names":["init","rootId","rootNode","document","querySelector","addEventListener","e","target","matches","Selectors","frequentComment","preventDefault","clicked","closest","criterion","remark","value","trim","innerHTML"],"mappings":"6LAwBA,uD,OAOoB,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,CAAiBC,UAAUC,eAA3B,CAAL,CAAkD,CAC9C,MACH,CAEDJ,CAAC,CAACK,cAAF,GALsC,GAOhCC,CAAAA,CAAO,CAAGN,CAAC,CAACC,MAAF,CAASM,OAAT,CAAiBJ,UAAUC,eAA3B,CAPsB,CAQhCI,CAAS,CAAGF,CAAO,CAACC,OAAR,CAAgBJ,UAAUK,SAA1B,CARoB,CAShCC,CAAM,CAAGD,CAAS,CAACV,aAAV,CAAwBK,UAAUM,MAAlC,CATuB,CAWtC,GAAI,CAACA,CAAL,CAAa,CACT,MACH,CAGD,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,CArBD,CAsBH,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 <http://www.gnu.org/licenses/>.\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 <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Selectors from './comments/selectors';\n\n/**\n * Manage the frequently used comments in the Marking Guide form.\n *\n * @param {String} rootId\n */\nexport const init = (rootId) => {\n const rootNode = document.querySelector(`#${rootId}`);\n\n rootNode.addEventListener('click', (e) => {\n if (!e.target.matches(Selectors.frequentComment)) {\n return;\n }\n\n e.preventDefault();\n\n const clicked = e.target.closest(Selectors.frequentComment);\n const criterion = clicked.closest(Selectors.criterion);\n const remark = criterion.querySelector(Selectors.remark);\n\n if (!remark) {\n return;\n }\n\n // Either append the comment to an existing comment or set it as the comment.\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"}

View File

@ -0,0 +1,2 @@
define ("gradingform_guide/grades/grader/gradingpanel/comments/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={frequentComment:"[data-gradingform_guide-role=\"frequent-comment\"]",criterion:"[data-gradingform-guide-role=\"criterion\"]",remark:"[data-gradingform-guide-role=\"remark\"]"};return a.default});
//# sourceMappingURL=selectors.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/grades/grader/gradingpanel/comments/selectors.js"],"names":["frequentComment","criterion","remark"],"mappings":"2LAuBe,CACXA,eAAe,CAAE,oDADN,CAEXC,SAAS,CAAE,6CAFA,CAGXC,MAAM,CAAE,0CAHG,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 <http://www.gnu.org/licenses/>.\n\n/**\n * Define all of the selectors we will be using on the Marking Guide interface.\n *\n * @module gradingform_guide/grades/grader/gradingpanel/comments/selectors\n * @package gradingform_guide\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n frequentComment: '[data-gradingform_guide-role=\"frequent-comment\"]',\n criterion: '[data-gradingform-guide-role=\"criterion\"]',\n remark: '[data-gradingform-guide-role=\"remark\"]',\n};\n"],"file":"selectors.min.js"}

View File

@ -0,0 +1,77 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Grading panel for gradingform_guide.
*
* @module gradingform_guide/grades/grader/gradingpanel
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
import {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';
// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()
import jQuery from 'jquery';
/**
* For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.
*
* @param {String} component
* @param {Number} contextid
* @param {String} itemname
* @param {Number} gradeduserid
*
* @returns {Promise}
*/
export const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {
return fetchMany([{
methodname: `gradingform_guide_grader_gradingpanel_fetch`,
args: {
component,
contextid,
itemname,
gradeduserid,
},
}])[0];
};
/**
* For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.
*
* @param {String} component
* @param {Number} contextid
* @param {String} itemname
* @param {Number} gradeduserid
* @param {HTMLElement} rootNode
*
* @returns {Promise}
*/
export const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, rootNode) => {
const form = rootNode.querySelector('form');
return normaliseResult(await fetchMany([{
methodname: `gradingform_guide_grader_gradingpanel_store`,
args: {
component,
contextid,
itemname,
gradeduserid,
formdata: jQuery(form).serialize(),
},
}])[0]);
};

View File

@ -0,0 +1,57 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Grading panel frequently used comments selector.
*
* @module gradingform_guide/grades/grader/gradingpanel/comments
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Selectors from './comments/selectors';
/**
* Manage the frequently used comments in the Marking Guide form.
*
* @param {String} rootId
*/
export const init = (rootId) => {
const rootNode = document.querySelector(`#${rootId}`);
rootNode.addEventListener('click', (e) => {
if (!e.target.matches(Selectors.frequentComment)) {
return;
}
e.preventDefault();
const clicked = e.target.closest(Selectors.frequentComment);
const criterion = clicked.closest(Selectors.criterion);
const remark = criterion.querySelector(Selectors.remark);
if (!remark) {
return;
}
// Either append the comment to an existing comment or set it as the comment.
if (remark.value.trim()) {
remark.value += `\n${clicked.innerHTML}`;
} else {
remark.value += clicked.innerHTML;
}
});
};

View File

@ -0,0 +1,28 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Define all of the selectors we will be using on the Marking Guide interface.
*
* @module gradingform_guide/grades/grader/gradingpanel/comments/selectors
* @package gradingform_guide
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
frequentComment: '[data-gradingform_guide-role="frequent-comment"]',
criterion: '[data-gradingform-guide-role="criterion"]',
remark: '[data-gradingform-guide-role="remark"]',
};

View File

@ -0,0 +1,292 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Web services relating to fetching of a marking guide for the grading panel.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace gradingform_guide\grades\grader\gradingpanel\external;
global $CFG;
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;
require_once($CFG->dirroot.'/grade/grading/form/guide/lib.php');
/**
* Web services relating to fetching of a marking guide for the grading panel.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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
* @throws \dml_exception
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
* @throws coding_exception
* @throws moodle_exception
* @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 (MARKING_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 gradeitem $gradeitem
* @param stdClass $gradeduser
* @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;
}
}

View File

@ -0,0 +1,169 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Web services relating to fetching of a marking guide for the grading panel.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace gradingform_guide\grades\grader\gradingpanel\external;
global $CFG;
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;
require_once($CFG->dirroot.'/grade/grading/form/guide/lib.php');
/**
* Web services relating to storing of a marking guide for the grading panel.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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
* @param string $formdata
* @return array
* @throws \dml_exception
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
* @throws coding_exception
* @throws moodle_exception
* @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 (MARKING_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();
}
}

View File

@ -0,0 +1,43 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* External functions and service definitions for the Marking Guide advanced grading form.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$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,
],
];

View File

@ -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.';

View File

@ -26,6 +26,9 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/grade/grading/form/lib.php');
/** guide: Used to compare our gradeitem_type against. */
const MARKING_GUIDE = 'guide';
/**
* This controller encapsulates the guide grading logic
*
@ -997,3 +1000,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',
];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

View File

@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm1.2-2c0 .5-.5 1-1 1h-.4c-.5 0-1-.5-1-1V7.4c0-.5.5-1 1-1h.5c.5 0 1 .5 1 1V12zm0-7.8c0 .7-.6 1.2-1.2 1.2-.7 0-1.2-.6-1.2-1.2C6.8 3.5 7.3 3 8 3s1.2.5 1.2 1.2z" fill="#999"/></svg>

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

View File

@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M11 4.5H7.5V1c0-.5-.5-1-1-1h-1c-.5 0-1 .5-1 1v3.5H1c-.5 0-1 .5-1 1v1c0 .5.5 1 1 1h3.5V11c0 .5.5 1 1 1h1c.5 0 1-.5 1-1V7.5H11c.6 0 1-.5 1-1v-1c0-.5-.4-1-1-1z" fill="#999"/></svg>

After

Width:  |  Height:  |  Size: 481 B

View File

@ -241,3 +241,8 @@
max-height: 80vh;
overflow-y: auto;
}
.gradingform_guide-frequent-comments {
position: absolute;
top: 7px;
right: 0px;
}

View File

@ -0,0 +1,5 @@
.gradingform_guide-fac {
position: absolute;
right: -5px;
top: 5px;
}

View File

@ -0,0 +1,134 @@
{{!
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 <http://www.gnu.org/licenses/>.
}}
{{!
@template gradingform_rubric/grades/grader/gradingpanel
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* instanceid: Instance of the module this grading form belongs too
* criterion: A gradeable item in the Marking Guide
* name: Name of the gradeable item
* id: ID of the gradeable item
* description: Description shown to students for this gradeable item
* descriptionmarkers: Description shown to teachers for this gradeable item
* maxscore: Max allowable assinable points for this item
* score: Current score assigned to the learner for this item
* remark: Text input for the teacher to relay to the student
* hascomments: Flag for frequently used comments
* comments: Array of frequently used comments
* description: Description of a frequently used comment
Example context (json):
{
"instanceid": "42",
"criterion": [
{
"name": "Motivation",
"id": 13,
"description": "Show your motivation to rock climbing",
"descriptionmarkers": "Does the student show interest in climbing?",
"maxscore": 37,
"score": 20,
"remark": "That's great!",
"hascomments": true,
"comments": [
{"description": "Great work!"},
{"description": "You should really try it before jumping to conclusions"}
]
}
]
}
}}
<form id="gradingform_guide-{{uniqid}}">
<input type="hidden" name="instanceid" value="{{instanceid}}">
{{#criterion}}
<div data-gradingform-guide-role="criterion">
<h5>
{{name}}
<a
href="#gradingform_guide-{{uniqid}}-criteria-{{id}}-description"
aria-controls="gradingform_guide-{{uniqid}}-criteria-{{id}}-description"
aria-expanded="false"
data-toggle="collapse"
role="button"
>
{{# pix }} info, gradingform_guide {{/ pix }}
</a>
</h5>
<div class="collapse" id="gradingform_guide-{{uniqid}}-criteria-{{id}}-description">
<div class="border p-3 mb-3 bg-white rounded">
{{{description}}}
{{#descriptionmarkers}}
<hr>
{{{descriptionmarkers}}}
{{/descriptionmarkers}}
</div>
</div>
<div class="form-group">
<label for="gradingform_guide-{{uniqid}}-criteria-{{id}}-score">{{#str}}outof, gradingform_guide, {{maxscore}}{{/str}}</label>
<input class="form-control" type="number" name="advancedgrading[criteria][{{id}}][score]" value="{{score}}"
id="gradingform_guide-{{uniqid}}-criteria-{{id}}-score"
aria-describedby="gradingform_guide-{{uniqid}}-help-{{id}}-score">
<small id="gradingform_guide-{{uniqid}}-help-{{id}}-score" class="sr-only">{{#str}}grade_help, gradingform_guide{{/str}}</small>
</div>
<div class="form-group ">
<label for="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark">{{#str}}additionalcomments, gradingform_guide{{/str}}</label>
<div class="input-group mb-3 form-inset form-inset-right">
<textarea class="form-control" type="text" name="advancedgrading[criteria][{{id}}][remark]"
id="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark"
aria-describedby="gradingform_guide-{{uniqid}}-help-{{id}}-remark"
data-gradingform-guide-role="remark"
>{{remark}}</textarea>
{{#hascomments}}
<a
class="form-inset-item"
href="#gradingform_guide-{{uniqid}}-criteria-{{id}}-remark-frequent-comments"
aria-controls="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark-frequent-comments"
aria-expanded="false"
data-toggle="collapse"
role="button"
>
{{#pix}}plus, gradingform_guide{{/pix}}
</a>
{{/hascomments}}
</div>
{{#hascomments}}
<div class="collapse" id="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark-frequent-comments">
<div data-gradingform_guide-frequent-comments="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark">
<div class="list-group">
{{#comments}}
<button type="button" class="list-group-item list-group-item-action" data-gradingform_guide-role="frequent-comment">{{description}}</button>
{{/comments}}
</div>
</div>
</div>
{{/hascomments}}
<small id="gradingform_guide-{{uniqid}}-help-{{id}}-remark" class="sr-only">{{#str}}grade_help, gradingform_guide{{/str}}</small>
</div>
</div>
{{/criterion}}
</form>
{{#js}}
require(['gradingform_guide/grades/grader/gradingpanel/comments'], function(Comments) {
Comments.init('gradingform_guide-{{uniqid}}');
});
{{/js}}

View File

@ -0,0 +1,41 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Coverage information for the gradingform_guide plugin.
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Coverage information for the gradingform_guide plugin.
*
* @package gradingform_guide
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
return new class extends phpunit_coverage_info {
// Array The list of folders relative to the plugin root to whitelist in coverage generation.
protected $whitelistfolders = [
'classes',
'tests/generator',
];
};

View File

@ -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');

View File

@ -0,0 +1,296 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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 gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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,
];
}
}

View File

@ -0,0 +1,249 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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 gradingform_guide
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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,
];
}
}

View File

@ -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;
$plugin->maturity = MATURITY_STABLE;

View File

@ -521,13 +521,53 @@ abstract class gradingform_controller {
* @param int $raterid
* @param int $itemid
* @return gradingform_instance
* @throws dml_exception
*/
public function get_or_create_instance($instanceid, $raterid, $itemid) {
global $DB;
if ($instanceid &&
$instance = $DB->get_record('grading_instances', array('id' => $instanceid, 'raterid' => $raterid, 'itemid' => $itemid), '*', IGNORE_MISSING)) {
return $this->get_instance($instance);
if (!is_numeric($instanceid)) {
$instanceid = null;
}
return $this->fetch_instance($raterid, $itemid, $instanceid);
}
/**
* If an instanceid is specified and grading instance exists and it is created by this rater for
* this item, then the instance is returned.
*
* If instanceid is not known, then null can be passed to fetch the current instance matchign the specified raterid
* and itemid.
*
* If the instanceid is falsey, or no instance was found, then create a new instance for the specified rater and item.
*
* @param int $raterid
* @param int $itemid
* @param int $instanceid
* @return gradingform_instance
* @throws dml_exception
*/
public function fetch_instance(int $raterid, int $itemid, ?int $instanceid): gradingform_instance {
global $DB;
$instance = null;
if (null === $instanceid) {
if ($instance = $this->get_current_instance($raterid, $itemid)) {
return $instance;
}
$instanceid = $instancerecord->id ?? null;
}
if (!empty($instanceid)) {
$instance = $DB->get_record('grading_instances', [
'id' => $instanceid,
'raterid' => $raterid,
'itemid' => $itemid,
], '*', IGNORE_MISSING);
if ($instance) {
return $this->get_instance($instance);
}
}
return $this->create_instance($raterid, $itemid);
}

View File

@ -0,0 +1,2 @@
define ("gradingform_rubric/grades/grader/gradingpanel",["exports","core/ajax","core_grades/grades/grader/gradingpanel/normalise","jquery"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;d=function(a){return a&&a.__esModule?a:{default:a}}(d);function e(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function f(a){return function(){var b=this,c=arguments;return new Promise(function(d,f){var i=a.apply(b,c);function g(a){e(i,d,f,g,h,"next",a)}function h(a){e(i,d,f,g,h,"throw",a)}g(void 0)})}}a.fetchCurrentGrade=function fetchCurrentGrade(a,c,d,e){return(0,b.call)([{methodname:"gradingform_rubric_grader_gradingpanel_fetch",args:{component:a,contextid:c,itemname:d,gradeduserid:e}}])[0]};var g=function(){var a=f(regeneratorRuntime.mark(function a(e,f,g,h,i){var j;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:j=i.querySelector("form");a.t0=c.normaliseResult;a.next=4;return(0,b.call)([{methodname:"gradingform_rubric_grader_gradingpanel_store",args:{component:e,contextid:f,itemname:g,gradeduserid:h,formdata:(0,d.default)(j).serialize()}}])[0];case 4:a.t1=a.sent;return a.abrupt("return",(0,a.t0)(a.t1));case 6:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=g});
//# sourceMappingURL=gradingpanel.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/grades/grader/gradingpanel.js"],"names":["fetchCurrentGrade","component","contextid","itemname","gradeduserid","methodname","args","storeCurrentGrade","rootNode","form","querySelector","normaliseResult","formdata","serialize"],"mappings":"2QA6BA,uD,mVAYiC,QAApBA,CAAAA,iBAAoB,CAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAC/E,MAAO,WAAU,CAAC,CACdC,UAAU,+CADI,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,CAaM,GAAMG,CAAAA,CAAiB,4CAAG,WAAMN,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,yFACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,MAGtBC,iBAHsB,gBAGA,WAAU,CAAC,CACpCN,UAAU,+CAD0B,CAEpCC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFQ,QAAQ,CAAE,cAAOH,CAAP,EAAaI,SAAb,EALR,CAF8B,CAAD,CAAV,EASzB,CATyB,CAHA,qGAAH,uDAAvB,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 <http://www.gnu.org/licenses/>.\n\n/**\n * Grading panel for gradingform_rubric.\n *\n * @module gradingform_rubric/grades/grader/gradingpanel\n * @package gradingform_rubric\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';\n\n\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n *\n * @returns {Promise}\n */\nexport const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `gradingform_rubric_grader_gradingpanel_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n * @param {HTMLElement} rootNode\n *\n * @returns {Promise}\n */\nexport const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, rootNode) => {\n const form = rootNode.querySelector('form');\n\n return normaliseResult(await fetchMany([{\n methodname: `gradingform_rubric_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"}

View File

@ -0,0 +1,78 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Grading panel for gradingform_rubric.
*
* @module gradingform_rubric/grades/grader/gradingpanel
* @package gradingform_rubric
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
import {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';
// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()
import jQuery from 'jquery';
/**
* For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.
*
* @param {String} component
* @param {Number} contextid
* @param {String} itemname
* @param {Number} gradeduserid
*
* @returns {Promise}
*/
export const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {
return fetchMany([{
methodname: `gradingform_rubric_grader_gradingpanel_fetch`,
args: {
component,
contextid,
itemname,
gradeduserid,
},
}])[0];
};
/**
* For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.
*
* @param {String} component
* @param {Number} contextid
* @param {String} itemname
* @param {Number} gradeduserid
* @param {HTMLElement} rootNode
*
* @returns {Promise}
*/
export const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, rootNode) => {
const form = rootNode.querySelector('form');
return normaliseResult(await fetchMany([{
methodname: `gradingform_rubric_grader_gradingpanel_store`,
args: {
component,
contextid,
itemname,
gradeduserid,
formdata: jQuery(form).serialize(),
},
}])[0]);
};

View File

@ -0,0 +1,298 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Web services relating to fetching of a rubric for the grading panel.
*
* @package gradingform_rubric
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace gradingform_rubric\grades\grader\gradingpanel\external;
global $CFG;
use coding_exception;
use context;
use core_grades\component_gradeitem as gradeitem;
use core_grades\component_gradeitems;
use external_api;
use external_function_parameters;
use external_multiple_structure;
use external_single_structure;
use external_value;
use external_warnings;
use stdClass;
use moodle_exception;
require_once($CFG->dirroot.'/grade/grading/form/rubric/lib.php');
/**
* Web services relating to fetching of a rubric for the grading panel.
*
* @package gradingform_rubric
* @copyright 2019 Mathew May <mathew.solutions>
* @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 {
[
'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 (RUBRIC !== $gradeitem->get_advanced_grading_method()) {
throw new moodle_exception(
"The {$itemname} item in {$component}/{$contextid} is not configured for advanced grading with a rubric"
);
}
// Fetch the actual data.
$gradeduser = \core_user::get_user($gradeduserid);
return self::get_fetch_data($gradeitem, $gradeduser);
}
/**
* Get the data to be fetched and create the structure ready for Mustache.
*
* @param gradeitem $gradeitem
* @param stdClass $gradeduser
* @return array
*/
public static function get_fetch_data(gradeitem $gradeitem, stdClass $gradeduser): array {
global $USER;
// Set up all the controllers etc that we'll be needing.
$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_rubric_filling();
$context = $controller->get_context();
$definitionid = (int) $definition->id;
$teacherdescription = self::get_formatted_text(
$context,
$definitionid,
'description',
$definition->description,
(int) $definition->descriptionformat
);
$criterion = [];
if ($definition->rubric_criteria) {
// Iterate over the defined criterion in the rubric and map out what we need to render each item.
$criterion = array_map(function($criterion) use ($definitionid, $fillings, $context) {
// The general structure we'll be returning, we still need to get the remark (if any) and the levels associated.
$result = [
'id' => $criterion['id'],
'description' => self::get_formatted_text(
$context,
$definitionid,
'description',
$criterion['description'],
(int) $criterion['descriptionformat']
),
];
// Do we have an existing grade filling? if so lets get the remark associated to this criteria.
$filling = [];
if (array_key_exists($criterion['id'], $fillings['criteria'])) {
$filling = $fillings['criteria'][$criterion['id']];
$result['remark'] = self::get_formatted_text($context,
$definitionid,
'remark',
$filling['remark'],
(int) $filling['remarkformat']
);
}
// Lets build the levels within a criteria and figure out what needs to go where.
$result['levels'] = array_map(function($level) use ($criterion, $filling, $context, $definitionid) {
// The bulk of what'll be returned can be defined easily we'll add to this further down.
$result = [
'id' => $level['id'],
'criterionid' => $criterion['id'],
'score' => $level['score'],
'definition' => self::get_formatted_text(
$context,
$definitionid,
'definition',
$level['definition'],
(int) $level['definitionformat']
),
'checked' => null,
];
// Consult the grade filling to see if a level has been selected and if it is the current level.
if (array_key_exists('levelid', $filling) && $filling['levelid'] == $level['id']) {
$result['checked'] = true;
}
return $result;
}, $criterion['levels']);
$nulllevel = [
'id' => null,
'criterionid' => $criterion['id'],
'score' => '-',
'definition' => 'Not set',
'checked' => null,
];
// Consult the grade filling to see if a level has been selected and if it is the current level.
if (array_key_exists('levelid', $filling) && $filling['levelid'] == 0) {
$nulllevel['checked'] = true;
}
array_unshift($result['levels'], $nulllevel);
return $result;
}, $definition->rubric_criteria);
}
return [
'templatename' => 'gradingform_rubric/grades/grader/gradingpanel',
'grade' => [
'instanceid' => $instance->get_id(),
'criteria' => $criterion,
'rubricmode' => 'evaluate editable',
'teacherdescription' => $teacherdescription,
'canedit' => false,
'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'),
'rubricmode' => new external_value(PARAM_RAW, 'The mode i.e. evaluate editable'),
'canedit' => new external_value(PARAM_BOOL, 'Can the user edit this'),
'criteria' => new external_multiple_structure(
new external_single_structure([
'id' => new external_value(PARAM_INT, 'ID of the Criteria'),
'description' => new external_value(PARAM_RAW, 'Description of the Criteria'),
'remark' => new external_value(PARAM_RAW, 'Any remarks for this criterion for the user being assessed', VALUE_OPTIONAL),
'levels' => new external_multiple_structure(new external_single_structure([
'id' => new external_value(PARAM_INT, 'ID of level'),
'criterionid' => new external_value(PARAM_INT, 'ID of the criterion this matches to'),
'score' => new external_value(PARAM_RAW, 'What this level is worth'),
'definition' => new external_value(PARAM_RAW, 'Definition of the level'),
'checked' => new external_value(PARAM_BOOL, 'Selected flag'),
])),
])
),
'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;
}
}

View File

@ -0,0 +1,166 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Web services relating to fetching of a rubric for the grading panel.
*
* @package gradingform_rubric
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace gradingform_rubric\grades\grader\gradingpanel\external;
global $CFG;
use coding_exception;
use context;
use core_grades\component_gradeitem as gradeitem;
use core_grades\component_gradeitems;
use external_api;
use external_function_parameters;
use external_single_structure;
use external_value;
use moodle_exception;
require_once($CFG->dirroot.'/grade/grading/form/rubric/lib.php');
/**
* Web services relating to storing of a rubric for the grading panel.
*
* @package gradingform_rubric
* @copyright 2019 Mathew May <mathew.solutions>
* @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
* @param string $formdata
* @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 (RUBRIC !== $gradeitem->get_advanced_grading_method()) {
throw new moodle_exception(
"The {$itemname} item in {$component}/{$contextid} is not configured for advanced grading with a rubric"
);
}
// Parse the serialised string into an object.
$data = [];
parse_str($formdata, $data);
// Grade.
$gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data);
// Fetch the updated grade back out.
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
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();
}
}

View File

@ -0,0 +1,45 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Rubric external functions and service definitions.
*
* @package gradingform_rubric
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$functions = [
'gradingform_rubric_grader_gradingpanel_fetch' => [
'classname' => 'gradingform_rubric\\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_rubric_grader_gradingpanel_store' => [
'classname' => 'gradingform_rubric\\grades\\grader\\gradingpanel\\external\\store',
'methodname' => 'execute',
'description' => 'Store the grading data for a user from the grader grading panel.',
'type' => 'write',
'ajax' => true,
],
];

View File

@ -58,6 +58,7 @@ $string['lockzeropoints_help'] = 'This setting only applies if the sum of the mi
$string['name'] = 'Name';
$string['needregrademessage'] = 'The rubric definition was changed after this student had been graded. The student can not see this rubric until you check the rubric and update the grade.';
$string['pluginname'] = 'Rubric';
$string['pointsvalue'] = '{$a} points';
$string['previewrubric'] = 'Preview rubric';
$string['privacy:metadata:criterionid'] = 'An identifier for a specific criterion being graded.';
$string['privacy:metadata:fillingssummary'] = 'Stores information about the user\'s grade created by the rubric.';

View File

@ -25,6 +25,10 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/grade/grading/form/lib.php');
require_once($CFG->dirroot.'/lib/filelib.php');
/** rubric: Used to compare our gradeitem_type against. */
const RUBRIC = 'rubric';
/**
* This controller encapsulates the rubric grading logic

View File

@ -308,3 +308,7 @@
position: relative;
float: right;
}
.gradingpanel-gradingform_rubric [aria-checked="true"] {
border: 1px solid black;
}

View File

@ -0,0 +1,105 @@
{{!
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 <http://www.gnu.org/licenses/>.
}}
{{!
@template gradingform_rubric/grades/grader/gradingpanel
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* instanceid: Instance of the module this grading form belongs too
* criteria: A gradeable item in the Marking Guide
* id: The ID of the criteria
* description: Description of the criteria
* levels: The level that a criteria can be graded at
* criterionid: The ID of the criteria
* checked: Flag for if this is the currently selected level
* definition: Definition of the level
* remark: Text input for the teacher to relay to the student
Example context (json):
{
"instanceid": "42",
"criteria": [
{
"id": 13,
"description": "Show your motivation to rock climbing",
"levels": [
{
"criterionid": 13,
"checked": true,
"definition": "Great work!"
}
],
"remark": "That's great!"
}
]
}
}}
<form id="gradingform_rubric-{{uniqid}}">
<input type="hidden" name="instanceid" value="{{instanceid}}">
<div id="rubric-advancedgrading-{{uniqid}}" class="criterion">
{{#criteria}}
<div class="d-block mb-2">
<h5 class="d-inline px-0 font-weight-bold mb-0">{{{description}}}</h5>
<button class="d-inline btn p-0 font-weight-bold mb-0 pull-right collapse"
type="button"
data-toggle="collapse"
data-target="#criteria-{{id}}"
aria-expanded="true"
aria-controls="criteria-{{id}}">
</button>
</div>
<div class="collapse show" id="criteria-{{id}}">
{{#levels}}
<div class="form-check">
<input class="form-check-input level"
type="radio"
name="advancedgrading[criteria][{{criterionid}}][levelid]"
id="advancedgrading-criteria-{{criterionid}}-levels-{{id}}-definition"
value="{{id}}"
{{#checked}}
aria-checked="true"
tabindex="0"
checked
{{/checked}}
{{^checked}}
aria-checked="false"
tabindex="-1"
{{/checked}}
>
<label class="w-100" for="advancedgrading-criteria-{{criterionid}}-levels-{{id}}-definition">
<label class="font-weight-bold">
{{{definition}}}
</label>
<label class="pull-right font-weight-bold">
{{#str}}pointsvalue, gradingform_rubric, {{score}}{{/str}}
</label>
</label>
</div>
{{/levels}}
<div class="form-group">
<label for="advancedgrading-criteria-{{id}}-remark">Additional feedback</label>
<textarea class="form-control" name="advancedgrading[criteria][{{id}}][remark]" id="advancedgrading-criteria-{{id}}-remark" cols="10" rows="5">{{{remark}}}</textarea>
</div>
</div>
{{/criteria}}
</div>
</form>

View File

@ -0,0 +1,41 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Coverage information for the gradingform_rubric plugin.
*
* @package gradingform_rubric
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Coverage information for the gradingform_rubric plugin.
*
* @package gradingform_rubric
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
return new class extends phpunit_coverage_info {
// Array The list of folders relative to the plugin root to whitelist in coverage generation.
protected $whitelistfolders = [
'classes',
'tests/generator',
];
};

View File

@ -0,0 +1,316 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package gradingform_rubric
* @category test
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
declare(strict_types = 1);
namespace gradingform_rubric\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 gradingform_rubric
* @category test
* @copyright 2019 Mathew May <mathew.solutions>
* @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 rubric");
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_rubric/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('criteria', $result['grade']);
$criteria = $result['grade']['criteria'];
$this->assertCount(count($definition->rubric_criteria), $criteria);
foreach ($criteria as $criterion) {
$this->assertArrayHasKey('id', $criterion);
$criterionid = $criterion['id'];
$sourcecriterion = $definition->rubric_criteria[$criterionid];
$this->assertArrayHasKey('description', $criterion);
$this->assertEquals($sourcecriterion['description'], $criterion['description']);
$this->assertArrayHasKey('levels', $criterion);
$levels = $criterion['levels'];
foreach ($levels as $level) {
$levelid = $level['id'];
if (!isset($levelid)) {
continue;
}
$sourcelevel = $sourcecriterion['levels'][$levelid];
$this->assertArrayHasKey('criterionid', $level);
$this->assertEquals($criterionid, $level['criterionid']);
$this->assertArrayHasKey('checked', $level);
$this->assertArrayHasKey('definition', $level);
$this->assertEquals($sourcelevel['definition'], $level['definition']);
$this->assertArrayHasKey('score', $level);
$this->assertEquals($sourcelevel['score'], $level['score']);
}
}
}
/**
* 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();
$rubricgenerator = $generator->get_plugin_generator('gradingform_rubric');
[
'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 = $rubricgenerator->get_test_form_data($controller, (int) $student->id,
0, 'Too many mistakes. Please try again.',
2, 'Great number of pictures. Well done.'
);
$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_rubric/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('criteria', $result['grade']);
$criteria = $result['grade']['criteria'];
$this->assertCount(count($definition->rubric_criteria), $criteria);
foreach ($criteria as $criterion) {
$this->assertArrayHasKey('id', $criterion);
$criterionid = $criterion['id'];
$sourcecriterion = $definition->rubric_criteria[$criterionid];
$this->assertArrayHasKey('description', $criterion);
$this->assertEquals($sourcecriterion['description'], $criterion['description']);
$this->assertArrayHasKey('remark', $criterion);
$this->assertArrayHasKey('levels', $criterion);
$levels = $criterion['levels'];
foreach ($levels as $level) {
$levelid = $level['id'];
if (!isset($levelid)) {
continue;
}
$sourcelevel = $sourcecriterion['levels'][$levelid];
$this->assertArrayHasKey('criterionid', $level);
$this->assertEquals($criterionid, $level['criterionid']);
$this->assertArrayHasKey('checked', $level);
$this->assertArrayHasKey('definition', $level);
$this->assertEquals($sourcelevel['definition'], $level['definition']);
$this->assertArrayHasKey('score', $level);
$this->assertEquals($sourcelevel['score'], $level['score']);
}
}
$this->assertEquals(1, $criteria[0]['levels'][1]['checked']);
$this->assertEquals('Too many mistakes. Please try again.', $criteria[0]['remark']);
$this->assertEquals(1, $criteria[1]['levels'][3]['checked']);
$this->assertEquals('Great number of pictures. Well done.', $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 rubric.
*
* @return array
*/
protected function get_test_data(): array {
global $DB;
$this->resetAfterTest();
$generator = \testing_util::get_data_generator();
$rubricgenerator = $generator->get_plugin_generator('gradingform_rubric');
$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 = $rubricgenerator->get_test_rubric($forum->get_context(), 'forum', 'forum');
$definition = $controller->get_definition();
$DB->set_field('forum', 'grade_forum', count($definition->rubric_criteria), ['id' => $forum->get_id()]);
return [
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
];
}
}

View File

@ -0,0 +1,261 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package gradingform_rubric
* @category test
* @copyright 2019 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
declare(strict_types = 1);
namespace gradingform_rubric\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 gradingform_rubric
* @category test
* @copyright 2019 Mathew May <mathew.solutions>
* @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);
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();
$rubricgenerator = $generator->get_plugin_generator('gradingform_rubric');
[
'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 = $rubricgenerator->get_test_form_data($controller, (int) $student->id,
0, 'Too many mistakes. Please try again.',
2, 'Great number of pictures. Well done.'
);
$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_rubric/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('criteria', $result['grade']);
$criteria = $result['grade']['criteria'];
$this->assertCount(count($definition->rubric_criteria), $criteria);
foreach ($criteria as $criterion) {
$this->assertArrayHasKey('id', $criterion);
$criterionid = $criterion['id'];
$sourcecriterion = $definition->rubric_criteria[$criterionid];
$this->assertArrayHasKey('description', $criterion);
$this->assertEquals($sourcecriterion['description'], $criterion['description']);
$this->assertArrayHasKey('remark', $criterion);
$this->assertArrayHasKey('levels', $criterion);
$levels = $criterion['levels'];
foreach ($levels as $level) {
$levelid = $level['id'];
if (!isset($levelid)) {
continue;
}
$sourcelevel = $sourcecriterion['levels'][$levelid];
$this->assertArrayHasKey('criterionid', $level);
$this->assertEquals($criterionid, $level['criterionid']);
$this->assertArrayHasKey('checked', $level);
$this->assertArrayHasKey('definition', $level);
$this->assertEquals($sourcelevel['definition'], $level['definition']);
$this->assertArrayHasKey('score', $level);
$this->assertEquals($sourcelevel['score'], $level['score']);
}
}
$this->assertEquals(1, $criteria[0]['levels'][1]['checked']);
$this->assertEquals('Too many mistakes. Please try again.', $criteria[0]['remark']);
$this->assertEquals(1, $criteria[1]['levels'][3]['checked']);
$this->assertEquals('Great number of pictures. Well done.', $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 rubric.
*
* @return array
*/
protected function get_test_data(): array {
global $DB;
$this->resetAfterTest();
$generator = \testing_util::get_data_generator();
$rubricgenerator = $generator->get_plugin_generator('gradingform_rubric');
$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 = $rubricgenerator->get_test_rubric($forum->get_context(), 'forum', 'forum');
$definition = $controller->get_definition();
$DB->set_field('forum', 'grade_forum', count($definition->rubric_criteria), ['id' => $forum->get_id()]);
return [
'forum' => $forum,
'controller' => $controller,
'definition' => $definition,
'student' => $student,
'teacher' => $teacher,
];
}
}

View File

@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'gradingform_rubric';
$plugin->version = 2019052000;
$plugin->version = 2019052006;
$plugin->requires = 2019051100;

View File

@ -24,6 +24,8 @@
defined('MOODLE_INTERNAL') || die();
use core_grades\component_gradeitems;
/**
* Factory method returning an instance of the grading manager
*
@ -288,14 +290,29 @@ class grading_manager {
public static function available_areas($component) {
global $CFG;
if (component_gradeitems::defines_advancedgrading_itemnames_for_component($component)) {
$result = [];
foreach (component_gradeitems::get_advancedgrading_itemnames_for_component($component) as $itemnumber => $itemname) {
$result[$itemname] = get_string("gradeitem:{$itemname}", $component);
}
return $result;
}
list($plugintype, $pluginname) = core_component::normalize_component($component);
if ($component === 'core_grading') {
return array();
} else if ($plugintype === 'mod') {
return plugin_callback('mod', $pluginname, 'grading', 'areas_list', null, array());
$callbackfunction = "grading_areas_list";
if (component_callback_exists($component, $callbackfunction)) {
debugging(
"Components supporting advanced grading should be updated to implement the component_gradeitems class",
DEBUG_DEVELOPER
);
return component_callback($component, $callbackfunction, [], []);
}
} else {
throw new coding_exception('Unsupported area location');
}

View File

@ -0,0 +1,41 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Coverage information for the core_grading subsystem.
*
* @package core_grading
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Coverage information for the core_grading subsystem.
*
* @package core_grading
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
return new class extends phpunit_coverage_info {
// Array The list of folders relative to the plugin root to whitelist in coverage generation.
protected $whitelistfolders = [
'classes',
'tests/generator',
];
};

View File

@ -0,0 +1,35 @@
{{!
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 <http://www.gnu.org/licenses/>.
}}
{{!
@template core_grades/grades/grader/gradingpanel/point
Point-based grading template for use in the grading panel.
Context variables required for this template:
Example context (json):
{
"grade": 47
}
}}
<form>
<div class="form-group">
<label for="core_grades-grade-{{uniqid}}">{{#str}}grade, moodle{{/str}}</label>
<input class="form-control" type="number" name="grade" value="{{grade}}" id="core_grades-grade-{{uniqid}}" aria-describedby="core_grades-help-{{uniqid}}">
<small id="core_grades-help-{{uniqid}}" class="form-text text-muted">{{#str}}grade_help, core_grades{{/str}}</small>
</div>
</form>

View File

@ -0,0 +1,42 @@
{{!
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 <http://www.gnu.org/licenses/>.
}}
{{!
@template core_grades/grades/grader/gradingpanel/scale
Scale-based grading template for use in the grading panel.
Context variables required for this template:
Example context (json):
{
"value": 1,
"selected": true,
"title": "Motivational"
}
}}
<form>
<div class="form-group">
<label for="core_grades-grade-{{uniqid}}">{{#str}}grade, moodle{{/str}}</label>
<select class="form-control" name="grade" id="core_grades-grade-{{uniqid}}" aria-describedby="core_grades-help-{{uniqid}}">
<option value="-1">{{#str}} nograde, moodle{{/str}}</option>
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{title}}</option>
{{/options}}
</select>
<small id="core_grades-help-{{uniqid}}" class="form-text text-muted">{{#str}}grade_help, core_grades{{/str}}</small>
</div>
</form>

View File

@ -196,8 +196,8 @@ Feature: We can set the grade to pass value
| Forum name | Test Forum 1 |
| Description | Test |
| Ratings > Aggregate type | Average of ratings |
| Ratings > Grade to pass | 90 |
| Ratings > scale[modgrade_point] | 60 |
| Grade > Grade to pass | 90 |
Then I should see "The grade to pass can not be greater than the maximum possible grade 60"
Scenario: Set a valid grade to pass for forum activity
@ -206,10 +206,10 @@ Feature: We can set the grade to pass value
| Forum name | Test Forum 1 |
| Description | Test |
| Ratings > Aggregate type | Average of ratings |
| Grade > Grade to pass | 90 |
| Ratings > Grade to pass | 90 |
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I click on "Edit forum Test Forum 1" "link"
And I click on "Edit forum Rating grade for Test Forum 1" "link"
And I expand all fieldsets
Then the field "Grade to pass" matches value "90"
And I set the field "Grade to pass" to "80"
@ -217,7 +217,7 @@ Feature: We can set the grade to pass value
And I am on "Course 1" course homepage
And I follow "Test Forum 1"
And I follow "Edit settings"
And the field "Grade to pass" matches value "80"
And the field "Ratings > Grade to pass" matches value "80"
Scenario: Set a valid grade to pass for glossary activity
When I turn editing mode on

View File

@ -0,0 +1,653 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
declare(strict_types = 1);
namespace tests\core_grades {
use advanced_testcase;
use core_grades\component_gradeitems;
use coding_exception;
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class component_gradeitems_test extends advanced_testcase {
/**
* Ensure that a component which does not implement the mapping class excepts.
*/
public function test_get_itemname_mapping_for_component_does_not_exist(): void {
$mappings = component_gradeitems::get_itemname_mapping_for_component('invalid_component');
$this->assertIsArray($mappings);
$this->assertCount(1, $mappings);
$this->assertArrayHasKey(0, $mappings);
}
/**
* Ensure that a component which does not implement the mapping class correctly excepts.
*/
public function test_get_itemname_mapping_for_valid_component_invalid_mapping(): void {
$this->expectException(coding_exception::class);
component_gradeitems::get_itemname_mapping_for_component('tests\core_grades\component_gradeitems\invalid');
}
/**
* Ensure that a component which implements the mapping class correctly eets the correct set of mappings.
*/
public function test_get_itemname_mapping_for_valid_component_valid_mapping(): void {
$mapping = component_gradeitems::get_itemname_mapping_for_component('tests\core_grades\component_gradeitems\valid');
$this->assertIsArray($mapping);
$this->assertEquals([
0 => 'rating',
1 => 'someother',
], $mapping);
}
/**
* Data provider for is_valid_itemname tests.
*
* @return array
*/
public function is_valid_itemname_provider(): array {
return [
'valid' => [
'someother',
true,
],
'validnotadvanced' => [
'rating',
true,
],
'invalid' => [
'doesnotexist',
false,
],
];
}
/**
* Ensure that a component implementing advanced grading returns the correct areas.
*
* @dataProvider is_valid_itemname_provider
* @param string $itemname
* @param bool $isadvanced
*/
public function test_is_valid_itemname(string $itemname, bool $isadvanced): void {
$this->assertEquals(
$isadvanced,
component_gradeitems::is_valid_itemname('tests\core_grades\component_gradeitems\valid_and_advanced', $itemname)
);
}
/**
* Ensure that a component which does not implement the advancedgrading interface returns this.
*/
public function test_defines_advancedgrading_itemnames_for_component_does_not_exist(): void {
$this->assertFalse(component_gradeitems::defines_advancedgrading_itemnames_for_component('invalid_component'));
}
/**
* Ensure that a component which does not implement the advancedgrading interface returns this.
*/
public function test_defines_advancedgrading_itemnames_for_component_no_interfaces(): void {
$this->assertFalse(component_gradeitems::defines_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\invalid'));
}
/**
* Ensure that a component which implements the item mapping but not implement the advancedgrading interface returns this.
*/
public function test_defines_advancedgrading_itemnames_for_component_grading_no_interface(): void {
$this->assertFalse(component_gradeitems::defines_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\valid'));
}
/**
* Ensure that a component which implements the item mapping but not implement the advancedgrading interface returns this.
*/
public function test_defines_advancedgrading_itemnames_for_component_grading_has_interface(): void {
$this->assertTrue(component_gradeitems::defines_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\valid_and_advanced'));
}
/**
* Ensure that a component which does not implement the advancedgrading interface returns this.
*/
public function test_get_advancedgrading_itemnames_for_component_does_not_exist(): void {
$this->expectException(coding_exception::class);
component_gradeitems::get_advancedgrading_itemnames_for_component('invalid_component');
}
/**
* Ensure that a component which does not implement the advancedgrading interface returns this.
*/
public function test_get_advancedgrading_itemnames_for_component_no_interfaces(): void {
$this->expectException(coding_exception::class);
component_gradeitems::get_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\invalid');
}
/**
* Ensure that a component which implements the item mapping but not implement the advancedgrading interface returns this.
*/
public function test_get_advancedgrading_itemnames_for_component_grading_no_interface(): void {
$this->expectException(coding_exception::class);
component_gradeitems::get_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\valid');
}
/**
* Ensure that a component implementing advanced grading returns the correct areas.
*/
public function test_get_advancedgrading_itemnames_for_component(): void {
$areas = component_gradeitems::get_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\valid_and_advanced');
$this->assertEquals(['someother'], $areas);
}
/**
* Data provider for is_advancedgrading_itemname tests.
*
* @return array
*/
public function is_advancedgrading_itemname_provider(): array {
return [
'valid' => [
'someother',
true,
],
'validnotadvanced' => [
'rating',
false,
],
'invalid' => [
'doesnotexist',
false,
],
];
}
/**
* Ensure that a component implementing advanced grading returns the correct areas.
*
* @dataProvider is_advancedgrading_itemname_provider
* @param string $itemname
* @param bool $isadvanced
*/
public function test_is_advancedgrading_itemname(string $itemname, bool $isadvanced): void {
$this->assertEquals(
$isadvanced,
component_gradeitems::is_advancedgrading_itemname('tests\core_grades\component_gradeitems\valid_and_advanced', $itemname)
);
}
/**
* Data provider for get_field_name_for_itemnumber.
*
* @return array
*/
public function get_field_name_for_itemnumber_provider(): array {
return [
'Valid itemnumber 0 case 1' => [
0,
'gradecat',
'gradecat',
],
'Valid itemnumber 0 case 2' => [
0,
'melon',
'melon',
],
'Valid itemnumber 1 case 1' => [
1,
'gradecat',
'gradecat_someother',
],
'Valid itemnumber 1 case 2' => [
1,
'melon',
'melon_someother',
],
];
}
/**
* Ensure that valid field names are correctly mapped for a valid component.
*
* @dataProvider get_field_name_for_itemnumber_provider
* @param int $itemnumber The item itemnumber to test
* @param string $fieldname The field name being translated
* @param string $expected The expected value
*/
public function test_get_field_name_for_itemnumber(int $itemnumber, string $fieldname, string $expected): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->assertEquals($expected, component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, $fieldname));
}
/**
* Ensure that an invalid itemnumber does not provide any field name.
*/
public function test_get_field_name_for_itemnumber_invalid_itemnumber(): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->expectException(coding_exception::class);
component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
}
/**
* Ensure that a component which does not define a mapping can still get a mapping for itemnumber 0.
*/
public function test_get_field_name_for_itemnumber_component_not_defining_mapping_itemnumber_zero(): void {
$component = 'tests\core_grades\othervalid';
$this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemnumber($component, 0, 'gradecat'));
}
/**
* Ensure that a component which does not define a mapping cannot get a mapping for itemnumber 1+.
*/
public function test_get_field_name_for_itemnumber_component_not_defining_mapping_itemnumber_nonzero(): void {
$component = 'tests\core_grades\othervalid';
$this->expectException(coding_exception::class);
component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
}
/**
* Ensure that a component which incorrectly defines a mapping cannot get a mapping for itemnumber 1+.
*/
public function test_get_field_name_for_itemnumber_component_invalid_mapping_itemnumber_nonzero(): void {
$component = 'tests\core_grades\component_gradeitems\invalid';
$this->expectException(coding_exception::class);
component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
}
/**
* Data provider for get_field_name_for_itemname.
*
* @return array
*/
public function get_field_name_for_itemname_provider(): array {
return [
'Empty itemname empty case 1' => [
'',
'gradecat',
'gradecat',
],
'Empty itemname empty case 2' => [
'',
'melon',
'melon',
],
'First itemname empty case 1' => [
'rating',
'gradecat',
'gradecat',
],
'First itemname empty case 2' => [
'rating',
'melon',
'melon',
],
'Other itemname empty case 1' => [
'someother',
'gradecat',
'gradecat_someother',
],
'Other itemname empty case 2' => [
'someother',
'melon',
'melon_someother',
],
];
}
/**
* Ensure that valid field names are correctly mapped for a valid component.
*
* @dataProvider get_field_name_for_itemname_provider
* @param string $itemname The item itemname to test
* @param string $fieldname The field name being translated
* @param string $expected The expected value
*/
public function test_get_field_name_for_itemname(string $itemname, string $fieldname, string $expected): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->assertEquals($expected, component_gradeitems::get_field_name_for_itemname($component, $itemname, $fieldname));
}
/**
* Ensure that an invalid itemname does not provide any field name.
*/
public function test_get_field_name_for_itemname_invalid_itemname(): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->expectException(coding_exception::class);
component_gradeitems::get_field_name_for_itemname($component, 'typo', 'gradecat');
}
/**
* Ensure that an empty itemname provides a matching fieldname regardless of whether the component exists or
* not.
*/
public function test_get_field_name_for_itemname_not_defining_mapping_empty_name(): void {
$component = 'tests\core_grades\othervalid';
$this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemname($component, '', 'gradecat'));
}
/**
* Ensure that an valid component with some itemname excepts.
*/
public function test_get_field_name_for_itemname_not_defining_mapping_with_name(): void {
$component = 'tests\core_grades\othervalid';
$this->expectException(coding_exception::class);
component_gradeitems::get_field_name_for_itemname($component, 'example', 'gradecat');
}
/**
* Ensure that an empty itemname provides a matching fieldname even if the mapping is invalid.
*/
public function test_get_field_name_for_itemname_invalid_mapping_empty_name(): void {
$component = 'tests\core_grades\component_gradeitems\invalid';
$this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemname($component, '', 'gradecat'));
}
/**
* Ensure that an invalid mapping with some itemname excepts.
*/
public function test_get_field_name_for_itemname_invalid_mapping_with_name(): void {
$component = 'tests\core_grades\component_gradeitems\invalid';
$this->expectException(coding_exception::class);
component_gradeitems::get_field_name_for_itemname($component, 'example', 'gradecat');
}
/**
* Data provider for get_itemname_from_itemnumber.
*
* @return array
*/
public function get_itemname_from_itemnumber_provider(): array {
return [
'Valid itemnumber 0' => [
0,
'',
],
'Valid itemnumber 1' => [
1,
'someother',
],
];
}
/**
* Ensure that item names are correctly mapped for a valid component.
*
* @dataProvider get_itemname_from_itemnumber_provider
* @param int $itemnumber The item itemnumber to test
* @param string $expected The expected value
*/
public function test_get_itemname_from_itemnumber(int $itemnumber, string $expected): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->assertEquals($expected, component_gradeitems::get_itemname_from_itemnumber($component, $itemnumber));
}
/**
* Ensure that an itemnumber over 1000 is treated as itemnumber 0 for the purpose of outcomes.
*/
public function test_get_itemname_from_itemnumber_outcome_itemnumber(): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->assertEquals('', component_gradeitems::get_itemname_from_itemnumber($component, 1000));
}
/**
* Ensure that an invalid itemnumber does not provide any field name.
*/
public function test_get_itemname_from_itemnumber_invalid_itemnumber(): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->expectException(coding_exception::class);
component_gradeitems::get_itemname_from_itemnumber($component, 100);
}
/**
* Ensure that a component which does not define a mapping can still get a mapping for itemnumber 0.
*/
public function test_get_itemname_from_itemnumber_component_not_defining_mapping_itemnumber_zero(): void {
$component = 'tests\core_grades\othervalid';
$this->assertEquals('', component_gradeitems::get_itemname_from_itemnumber($component, 0));
}
/**
* Ensure that a component which does not define a mapping cannot get a mapping for itemnumber 1+.
*/
public function test_get_itemname_from_itemnumber_component_not_defining_mapping_itemnumber_nonzero(): void {
$component = 'tests\core_grades\othervalid';
$this->expectException(coding_exception::class);
component_gradeitems::get_itemname_from_itemnumber($component, 100);
}
/**
* Ensure that a component which incorrectly defines a mapping cannot get a mapping for itemnumber 1+.
*/
public function test_get_itemname_from_itemnumber_component_invalid_mapping_itemnumber_nonzero(): void {
$component = 'tests\core_grades\component_gradeitems\invalid';
$this->expectException(coding_exception::class);
component_gradeitems::get_itemname_from_itemnumber($component, 100);
}
/**
* Data provider for get_itemname_from_itemnumber.
*
* @return array
*/
public function get_itemnumber_from_itemname_provider(): array {
return [
'Empty itemname empty' => [
'',
0,
],
'First itemname empty' => [
'rating',
0,
],
'Other itemname empty' => [
'someother',
1,
],
];
}
/**
* Ensure that valid item names are correctly mapped for a valid component.
*
* @dataProvider get_itemnumber_from_itemname_provider
* @param string $itemname The item itemname to test
* @param int $expected The expected value
*/
public function test_get_itemnumber_from_itemname(string $itemname, int $expected): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->assertEquals($expected, component_gradeitems::get_itemnumber_from_itemname($component, $itemname));
}
/**
* Ensure that an invalid itemname excepts.
*/
public function test_get_itemnumber_from_itemname_invalid_itemname(): void {
$component = 'tests\core_grades\component_gradeitems\valid';
$this->expectException(coding_exception::class);
component_gradeitems::get_itemnumber_from_itemname($component, 'typo');
}
/**
* Ensure that an empty itemname provides a correct itemnumber regardless of whether the component exists or
* not.
*/
public function test_get_itemnumber_from_itemname_not_defining_mapping_empty_name(): void {
$component = 'tests\core_grades\component_gradeitems\othervalid';
$this->assertEquals(0, component_gradeitems::get_itemnumber_from_itemname($component, ''));
}
/**
* Ensure that an valid component with some itemname excepts.
*/
public function test_get_itemnumber_from_itemname_not_defining_mapping_with_name(): void {
$component = 'tests\core_grades\component_gradeitems\othervalid';
$this->expectException(coding_exception::class);
component_gradeitems::get_itemnumber_from_itemname($component, 'example');
}
/**
* Ensure that an empty itemname provides a matching fieldname even if the mapping is invalid.
*/
public function test_get_itemnumber_from_itemname_invalid_mapping_empty_name(): void {
$component = 'tests\core_grades\component_gradeitems\invalid';
$this->assertEquals(0, component_gradeitems::get_itemnumber_from_itemname($component, ''));
}
/**
* Ensure that an invalid mapping with some itemname excepts.
*/
public function test_get_itemnumber_from_itemname_invalid_mapping_with_name(): void {
$component = 'tests\core_grades\component_gradeitems\invalid';
$this->expectException(coding_exception::class);
component_gradeitems::get_itemnumber_from_itemname($component, 'example');
}
}
}
namespace tests\core_grades\component_gradeitems\valid\grades {
use core_grades\local\gradeitem\itemnumber_mapping;
/**
* Valid class for testing mappings.
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradeitems implements itemnumber_mapping {
/**
* Get the grade item mapping of item number to item name.
*
* @return array
*/
public static function get_itemname_mapping_for_component(): array {
return [
0 => 'rating',
1 => 'someother',
];
}
}
}
namespace tests\core_grades\component_gradeitems\valid_and_advanced\grades {
use core_grades\local\gradeitem\itemnumber_mapping;
use core_grades\local\gradeitem\advancedgrading_mapping;
/**
* Valid class for testing mappings.
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradeitems implements itemnumber_mapping, advancedgrading_mapping {
/**
* Get the grade item mapping of item number to item name.
*
* @return array
*/
public static function get_itemname_mapping_for_component(): array {
return [
0 => 'rating',
1 => 'someother',
];
}
/**
* Get the list of items which define advanced grading.
*
* @return array
*/
public static function get_advancedgrading_itemnames(): array {
return [
'someother',
];
}
}
}
namespace tests\core_grades\component_gradeitems\invalid\grades {
use core_grades\local\gradeitem\itemnumber_mapping;
/**
* Invalid class for testing mappings.
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradeitems {
/**
* Get the grade item mapping of item number to item name.
*
* @return array
*/
public static function get_itemname_mapping_for_component(): array {
return [
0 => 'rating',
1 => 'someother',
];
}
/**
* Get the list of items which define advanced grading.
*
* @return array
*/
public static function get_advancedgrading_itemnames(): array {
return [
1 => 'someother',
];
}
}
}

40
grade/tests/coverage.php Normal file
View File

@ -0,0 +1,40 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Coverage information for the grades component.
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Coverage information for the core_grades subsystem.
*
* @package core_grades
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
return new class extends phpunit_coverage_info {
// Array The list of folders relative to the plugin root to whitelist in coverage generation.
protected $whitelistfolders = [
'classes',
];
};

View File

@ -0,0 +1,200 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
declare(strict_types = 1);
namespace core_grades\grades\grader\gradingpanel\point\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 <andrew@nicols.co.uk>
* @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' => -1,
]);
$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 direct grading");
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 = $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');
$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('core_grades/grades/grader/gradingpanel/point', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('grade', $result['grade']);
$this->assertEmpty($result['grade']['grade']);
$this->assertArrayHasKey('timecreated', $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']);
}
/**
* 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();
$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');
$gradeitem->store_grade_from_formdata($student, $teacher, (object) [
'grade' => 4,
]);
$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('core_grades/grades/grader/gradingpanel/point', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('grade', $result['grade']);
$this->assertIsFloat($result['grade']['grade']);
$this->assertEquals(grade_floatval(unformat_float(4)), $result['grade']['grade']);
$this->assertArrayHasKey('timecreated', $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']);
}
/**
* 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);
}
}

View File

@ -0,0 +1,315 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
declare(strict_types = 1);
namespace core_grades\grades\grader\gradingpanel\point\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;
use grade_grade;
use grade_item;
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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([
// Negative numbers mean a scale.
'grade_forum' => -1,
]);
$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 direct grading");
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_empty(): 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);
$formdata = [
'grade' => null,
];
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata));
$result = external_api::clean_returnvalue(store::execute_returns(), $result);
// The result should still be empty.
$this->assertIsArray($result);
$this->assertArrayHasKey('templatename', $result);
$this->assertEquals('core_grades/grades/grader/gradingpanel/point', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('grade', $result['grade']);
$this->assertEmpty($result['grade']['grade']);
$this->assertArrayHasKey('timecreated', $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']);
// Compare against the grade stored in the database.
$storedgradeitem = grade_item::fetch([
'courseid' => $forum->get_course_id(),
'itemtype' => 'mod',
'itemmodule' => 'forum',
'iteminstance' => $forum->get_id(),
'itemnumber' => $gradeitem->get_grade_itemid(),
]);
$storedgrade = grade_grade::fetch([
'userid' => $student->id,
'itemid' => $storedgradeitem->id,
]);
$this->assertEmpty($storedgrade->rawgrade);
}
/**
* 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();
$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);
$formdata = [
'grade' => 4,
];
$formattedvalue = grade_floatval(unformat_float(4));
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata));
$result = external_api::clean_returnvalue(store::execute_returns(), $result);
// The result should still be empty.
$this->assertIsArray($result);
$this->assertArrayHasKey('templatename', $result);
$this->assertEquals('core_grades/grades/grader/gradingpanel/point', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('grade', $result['grade']);
$this->assertEquals($formattedvalue, $result['grade']['grade']);
$this->assertArrayHasKey('timecreated', $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']);
// Compare against the grade stored in the database.
$storedgradeitem = grade_item::fetch([
'courseid' => $forum->get_course_id(),
'itemtype' => 'mod',
'itemmodule' => 'forum',
'iteminstance' => $forum->get_id(),
'itemnumber' => $gradeitem->get_grade_itemid(),
]);
$storedgrade = grade_grade::fetch([
'userid' => $student->id,
'itemid' => $storedgradeitem->id,
]);
$this->assertEquals($formattedvalue, $storedgrade->rawgrade);
}
/**
* Ensure that an out-of-range value is rejected.
*
* @dataProvider execute_out_of_range_provider
* @param int $maxvalue The max value of the forum
* @param int $suppliedvalue The value that was submitted
*/
public function test_execute_store_out_of__range(int $maxvalue, int $suppliedvalue): void {
$this->resetAfterTest();
$forum = $this->get_forum_instance([
// Negative numbers mean a scale.
'grade_forum' => $maxvalue,
]);
$course = $forum->get_course_record();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->setUser($teacher);
$formdata = [
'grade' => $suppliedvalue,
];
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage("Invalid grade '{$suppliedvalue}' provided. Grades must be between 0 and {$maxvalue}.");
store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata));
}
/**
* Data provider for out of range tests.
*
* @return array
*/
public function execute_out_of_range_provider(): array {
return [
'above' => [
'max' => 100,
'supplied' => 101,
],
'above just' => [
'max' => 100,
'supplied' => 101.001,
],
'below' => [
'max' => 100,
'supplied' => -100,
],
'-1' => [
'max' => 100,
'supplied' => -1,
],
];
}
/**
* 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);
}
}

View File

@ -0,0 +1,255 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
declare(strict_types = 1);
namespace core_grades\grades\grader\gradingpanel\scale\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 <andrew@nicols.co.uk>
* @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 grading with scales");
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();
$options = [
'A',
'B',
'C'
];
$scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]);
$forum = $this->get_forum_instance([
// Negative numbers mean a scale.
'grade_forum' => -1 * $scale->id
]);
$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');
$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('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('options', $result['grade']);
$this->assertCount(count($options), $result['grade']['options']);
rsort($options);
foreach ($options as $index => $option) {
$this->assertArrayHasKey($index, $result['grade']['options']);
$returnedoption = $result['grade']['options'][$index];
$this->assertArrayHasKey('value', $returnedoption);
$this->assertEquals(3 - $index, $returnedoption['value']);
$this->assertArrayHasKey('title', $returnedoption);
$this->assertEquals($option, $returnedoption['title']);
$this->assertArrayHasKey('selected', $returnedoption);
$this->assertFalse($returnedoption['selected']);
}
$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']);
}
/**
* 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();
$options = [
'A',
'B',
'C'
];
$scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]);
$forum = $this->get_forum_instance([
// Negative numbers mean a scale.
'grade_forum' => -1 * $scale->id
]);
$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');
$gradeitem->store_grade_from_formdata($student, $teacher, (object) [
'grade' => 2,
]);
$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('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
$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('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('options', $result['grade']);
$this->assertCount(count($options), $result['grade']['options']);
rsort($options);
foreach ($options as $index => $option) {
$this->assertArrayHasKey($index, $result['grade']['options']);
$returnedoption = $result['grade']['options'][$index];
$this->assertArrayHasKey('value', $returnedoption);
$this->assertEquals(3 - $index, $returnedoption['value']);
$this->assertArrayHasKey('title', $returnedoption);
$this->assertEquals($option, $returnedoption['title']);
$this->assertArrayHasKey('selected', $returnedoption);
}
// The grade was 2, which relates to the middle option.
$this->assertFalse($result['grade']['options'][0]['selected']);
$this->assertTrue($result['grade']['options'][1]['selected']);
$this->assertFalse($result['grade']['options'][2]['selected']);
$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']);
}
/**
* 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);
}
}

View File

@ -0,0 +1,443 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
declare(strict_types = 1);
namespace core_grades\grades\grader\gradingpanel\scale\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;
use grade_grade;
use grade_item;
/**
* Unit tests for core_grades\component_gradeitems;
*
* @package core_grades
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @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([
// 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 grading with scales");
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_empty(): void {
[
'forum' => $forum,
'options' => $options,
'student' => $student,
'teacher' => $teacher,
] = $this->get_test_data();
$this->setUser($teacher);
$formdata = [
'grade' => null,
];
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata));
$result = external_api::clean_returnvalue(store::execute_returns(), $result);
// The result should still be empty.
$this->assertIsArray($result);
$this->assertArrayHasKey('templatename', $result);
$this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('options', $result['grade']);
$this->assertCount(count($options), $result['grade']['options']);
rsort($options);
foreach ($options as $index => $option) {
$this->assertArrayHasKey($index, $result['grade']['options']);
$returnedoption = $result['grade']['options'][$index];
$this->assertArrayHasKey('value', $returnedoption);
$this->assertEquals(3 - $index, $returnedoption['value']);
$this->assertArrayHasKey('title', $returnedoption);
$this->assertEquals($option, $returnedoption['title']);
$this->assertArrayHasKey('selected', $returnedoption);
$this->assertFalse($returnedoption['selected']);
}
$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']);
// Compare against the grade stored in the database.
$storedgradeitem = grade_item::fetch([
'courseid' => $forum->get_course_id(),
'itemtype' => 'mod',
'itemmodule' => 'forum',
'iteminstance' => $forum->get_id(),
'itemnumber' => $gradeitem->get_grade_itemid(),
]);
$storedgrade = grade_grade::fetch([
'userid' => $student->id,
'itemid' => $storedgradeitem->id,
]);
$this->assertEmpty($storedgrade->rawgrade);
}
/**
* Ensure that an execute against the correct grading method returns the current state of the user.
*/
public function test_execute_store_not_selected(): void {
[
'forum' => $forum,
'options' => $options,
'student' => $student,
'teacher' => $teacher,
] = $this->get_test_data();
$this->setUser($teacher);
$formdata = [
'grade' => -1,
];
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata));
$result = external_api::clean_returnvalue(store::execute_returns(), $result);
// The result should still be empty.
$this->assertIsArray($result);
$this->assertArrayHasKey('templatename', $result);
$this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('options', $result['grade']);
$this->assertCount(count($options), $result['grade']['options']);
rsort($options);
foreach ($options as $index => $option) {
$this->assertArrayHasKey($index, $result['grade']['options']);
$returnedoption = $result['grade']['options'][$index];
$this->assertArrayHasKey('value', $returnedoption);
$this->assertEquals(3 - $index, $returnedoption['value']);
$this->assertArrayHasKey('title', $returnedoption);
$this->assertEquals($option, $returnedoption['title']);
$this->assertArrayHasKey('selected', $returnedoption);
$this->assertFalse($returnedoption['selected']);
}
$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']);
// Compare against the grade stored in the database.
$storedgradeitem = grade_item::fetch([
'courseid' => $forum->get_course_id(),
'itemtype' => 'mod',
'itemmodule' => 'forum',
'iteminstance' => $forum->get_id(),
'itemnumber' => $gradeitem->get_grade_itemid(),
]);
$storedgrade = grade_grade::fetch([
'userid' => $student->id,
'itemid' => $storedgradeitem->id,
]);
// No grade will have been saved.
$this->assertFalse($storedgrade);
}
/**
* Ensure that an execute against the correct grading method returns the current state of the user.
*/
public function test_execute_store_graded(): void {
[
'scale' => $scale,
'forum' => $forum,
'options' => $options,
'student' => $student,
'teacher' => $teacher,
] = $this->get_test_data();
$this->setUser($teacher);
$formdata = [
'grade' => 2,
];
$formattedvalue = grade_floatval(unformat_float($formdata['grade']));
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata));
$result = external_api::clean_returnvalue(store::execute_returns(), $result);
// The result should still be empty.
$this->assertIsArray($result);
$this->assertArrayHasKey('templatename', $result);
$this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
$this->assertArrayHasKey('grade', $result);
$this->assertIsArray($result['grade']);
$this->assertArrayHasKey('options', $result['grade']);
$this->assertCount(count($options), $result['grade']['options']);
rsort($options);
foreach ($options as $index => $option) {
$this->assertArrayHasKey($index, $result['grade']['options']);
$returnedoption = $result['grade']['options'][$index];
$this->assertArrayHasKey('value', $returnedoption);
$this->assertEquals(3 - $index, $returnedoption['value']);
$this->assertArrayHasKey('title', $returnedoption);
$this->assertEquals($option, $returnedoption['title']);
$this->assertArrayHasKey('selected', $returnedoption);
}
// The grade was 2, which relates to the middle option.
$this->assertFalse($result['grade']['options'][0]['selected']);
$this->assertTrue($result['grade']['options'][1]['selected']);
$this->assertFalse($result['grade']['options'][2]['selected']);
$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']);
// Compare against the grade stored in the database.
$storedgradeitem = grade_item::fetch([
'courseid' => $forum->get_course_id(),
'itemtype' => 'mod',
'itemmodule' => 'forum',
'iteminstance' => $forum->get_id(),
'itemnumber' => $gradeitem->get_grade_itemid(),
]);
$storedgrade = grade_grade::fetch([
'userid' => $student->id,
'itemid' => $storedgradeitem->id,
]);
$this->assertEquals($formattedvalue, $storedgrade->rawgrade);
$this->assertEquals($scale->id, $storedgrade->rawscaleid);
}
/**
* Ensure that an out-of-range value is rejected.
*
* @dataProvider execute_out_of_range_provider
* @param int $suppliedvalue The value that was submitted
*/
public function test_execute_store_out_of_range(int $suppliedvalue): void {
[
'scale' => $scale,
'forum' => $forum,
'options' => $options,
'student' => $student,
'teacher' => $teacher,
] = $this->get_test_data();
$this->setUser($teacher);
$formdata = [
'grade' => $suppliedvalue,
];
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage("Invalid grade '{$suppliedvalue}' provided. Grades must be between 0 and 3.");
store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata));
}
/**
* Data provider for out of range tests.
*
* @return array
*/
public function execute_out_of_range_provider(): array {
return [
'above' => [
'supplied' => 500,
],
'above just' => [
'supplied' => 4,
],
'below' => [
'supplied' => -100,
],
'-10' => [
'supplied' => -10,
],
];
}
/**
* 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 scaled forums.
*
* @return array
*/
protected function get_test_data(): array {
$this->resetAfterTest();
$options = [
'A',
'B',
'C'
];
$scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]);
$forum = $this->get_forum_instance([
// Negative numbers mean a scale.
'grade_forum' => -1 * $scale->id
]);
$course = $forum->get_course_record();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
return [
'forum' => $forum,
'scale' => $scale,
'options' => $options,
'student' => $student,
'teacher' => $teacher,
];
}
}

View File

@ -245,6 +245,7 @@ $string['full'] = 'Full';
$string['fullmode'] = 'Change to full view';
$string['generalsettings'] = 'General settings';
$string['grade'] = 'Grade';
$string['grade_help'] = 'The grade to award this person for their work.';
$string['gradeadministration'] = 'Grade administration';
$string['gradealreadyupdated'] = '{$a} grades have not been imported because the grades in the import file are older than in the grader report. To proceed with the grade import anyway, use the force import option.';
$string['gradeanalysis'] = 'Grade analysis';

View File

@ -94,3 +94,4 @@ $string['templatepickownform'] = 'Use this form as a template';
$string['templatetypeown'] = 'Own form';
$string['templatetypeshared'] = 'Shared template';
$string['templatesource'] = 'Location: {$a->component} ({$a->area})';
$string['error:notinrange'] = 'Invalid grade \'{$a->grade}\' provided. Grades must be between 0 and {$a->maxgrade}.';

View File

@ -512,6 +512,7 @@ $string['digitalminor_desc'] = 'Please ask your parent/guardian to contact:';
$string['directory'] = 'Directory';
$string['disable'] = 'Disable';
$string['disabledcomments'] = 'Comments are disabled';
$string['dismissnotification'] = 'Dismiss this notification';
$string['displayingfirst'] = 'Only the first {$a->count} {$a->things} are displayed';
$string['displayingrecords'] = 'Displaying {$a} records';
$string['displayingusers'] = 'Displaying users {$a->start} to {$a->end}';

View File

@ -1,2 +1,2 @@
define ("core/loadingicon",["jquery","core/templates"],function(a,b){var c={LOADING:"core/loading"},d=function(){return b.render(c.LOADING,{})},e=function(b,c){return d().then(function(d){var e=a(d).hide();b.append(e);e.fadeIn(150);return a.when(e.promise(),c)}).then(function(a){return a.fadeOut(100).promise()}).then(function(a){a.remove()})};return{getIcon:d,addIconToContainer:function addIconToContainer(b){return d().then(function(c){var d=a(c).hide();b.append(d);d.fadeIn(150);return d})},addIconToContainerWithPromise:function addIconToContainerWithPromise(b){var c=a.Deferred();e(b,c);return c},addIconToContainerRemoveOnCompletion:e}});
define ("core/loadingicon",["jquery","core/templates"],function(a,b){var c={LOADING:"core/loading"},d=function(){return b.render(c.LOADING,{})},e=function(b,c){return d().then(function(d){var e=a(d).hide();a(b).append(e);e.fadeIn(150);return a.when(e.promise(),c)}).then(function(a){return a.fadeOut(100).promise()}).then(function(a){a.remove()})};return{getIcon:d,addIconToContainer:function addIconToContainer(b){return d().then(function(c){var d=a(c).hide();a(b).append(d);d.fadeIn(150);return d})},addIconToContainerWithPromise:function addIconToContainerWithPromise(b){var c=a.Deferred();e(b,c);return c},addIconToContainerRemoveOnCompletion:e}});
//# sourceMappingURL=loadingicon.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
lib/amd/build/toast.min.js vendored Normal file
View File

@ -0,0 +1,2 @@
define ("core/toast",["exports","core/templates","core/notification","core/pending"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.add=a.addToastRegion=void 0;b=e(b);c=e(c);d=e(d);function e(a){return a&&a.__esModule?a:{default:a}}function f(a){for(var b=1;b<arguments.length;b++){var c=null!=arguments[b]?arguments[b]:{},d=Object.keys(c);if("function"==typeof Object.getOwnPropertySymbols){d=d.concat(Object.getOwnPropertySymbols(c).filter(function(a){return Object.getOwnPropertyDescriptor(c,a).enumerable}))}d.forEach(function(b){g(a,b,c[b])})}return a}function g(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}var j=function(){var a=i(regeneratorRuntime.mark(function a(e){var f,g,h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:f=new d.default("addToastRegion");a.prev=1;a.next=4;return b.default.renderForPromise("core/local/toast/wrapper",{});case 4:g=a.sent;h=g.html;i=g.js;b.default.prependNodeContents(e,h,i);a.next=13;break;case 10:a.prev=10;a.t0=a["catch"](1);c.default.exception(a.t0);case 13:f.resolve();case 14:case"end":return a.stop();}}},a,null,[[1,10]])}));return function(){return a.apply(this,arguments)}}();a.addToastRegion=j;var k=function(){var a=i(regeneratorRuntime.mark(function a(e,g){var h,i,j,k,m,n;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:h=new d.default("addToastRegion");g=f({closeButton:!1,autohide:!0,delay:4e3},g);i="core/local/toast/message";a.prev=3;a.next=6;return l();case 6:j=a.sent;a.next=9;return b.default.renderForPromise(i,f({message:e},g));case 9:k=a.sent;m=k.html;n=k.js;b.default.prependNodeContents(j,m,n);a.next=18;break;case 15:a.prev=15;a.t0=a["catch"](3);c.default.exception(a.t0);case 18:h.resolve();case 19:case"end":return a.stop();}}},a,null,[[3,15]])}));return function(){return a.apply(this,arguments)}}();a.add=k;var l=function(){var a=i(regeneratorRuntime.mark(function a(){var b;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:b=document.querySelector(".toast-wrapper");if(!b){a.next=3;break}return a.abrupt("return",b);case 3:a.next=5;return j(document.body,"fixed-bottom");case 5:return a.abrupt("return",l());case 6:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}()});
//# sourceMappingURL=toast.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../src/toast.js"],"names":["addToastRegion","parent","pendingPromise","Pending","Templates","renderForPromise","html","js","prependNodeContents","Notification","exception","resolve","add","message","configuration","closeButton","autohide","delay","templateName","getTargetNode","targetNode","region","document","querySelector","body"],"mappings":"mMAuBA,OACA,OACA,O,wzBAOO,GAAMA,CAAAA,CAAc,4CAAG,WAAMC,CAAN,+FACpBC,CADoB,CACH,GAAIC,UAAJ,CAAY,gBAAZ,CADG,yBAIGC,WAAUC,gBAAV,CAA2B,0BAA3B,CAAuD,EAAvD,CAJH,iBAIfC,CAJe,GAIfA,IAJe,CAITC,CAJS,GAITA,EAJS,CAKtBH,UAAUI,mBAAV,CAA8BP,CAA9B,CAAsCK,CAAtC,CAA4CC,CAA5C,EALsB,qDAOtBE,UAAaC,SAAb,OAPsB,QAU1BR,CAAc,CAACS,OAAf,GAV0B,uDAAH,uDAApB,C,mBAuBA,GAAMC,CAAAA,CAAG,4CAAG,WAAMC,CAAN,CAAeC,CAAf,mGACTZ,CADS,CACQ,GAAIC,UAAJ,CAAY,gBAAZ,CADR,CAEfW,CAAa,IACTC,WAAW,GADF,CAETC,QAAQ,GAFC,CAGTC,KAAK,CAAE,GAHE,EAINH,CAJM,CAAb,CAOMI,CATS,oDAWcC,CAAAA,CAAa,EAX3B,QAWLC,CAXK,uBAYchB,WAAUC,gBAAV,CAA2Ba,CAA3B,IACrBL,OAAO,CAAPA,CADqB,EAElBC,CAFkB,EAZd,iBAYJR,CAZI,GAYJA,IAZI,CAYEC,CAZF,GAYEA,EAZF,CAgBXH,UAAUI,mBAAV,CAA8BY,CAA9B,CAA0Cd,CAA1C,CAAgDC,CAAhD,EAhBW,qDAkBXE,UAAaC,SAAb,OAlBW,QAqBfR,CAAc,CAACS,OAAf,GArBe,uDAAH,uDAAT,C,QAwBP,GAAMQ,CAAAA,CAAa,4CAAG,oGACZE,CADY,CACHC,QAAQ,CAACC,aAAT,CAAuB,gBAAvB,CADG,KAGdF,CAHc,0CAIPA,CAJO,wBAOZrB,CAAAA,CAAc,CAACsB,QAAQ,CAACE,IAAV,CAAgB,cAAhB,CAPF,iCAQXL,CAAa,EARF,0CAAH,uD","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 <http://www.gnu.org/licenses/>.\n\n/**\n * A system for displaying small snackbar notifications to users which disappear shortly after they are shown.\n *\n * @module core/toast\n * @package core\n * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\n\n/**\n * Add a new region to place toasts in, taking in a parent element.\n *\n * @param {Element} parent\n */\nexport const addToastRegion = async(parent) => {\n const pendingPromise = new Pending('addToastRegion');\n\n try {\n const {html, js} = await Templates.renderForPromise('core/local/toast/wrapper', {});\n Templates.prependNodeContents(parent, html, js);\n } catch (e) {\n Notification.exception(e);\n }\n\n pendingPromise.resolve();\n};\n\n/**\n * Add a new toast or snackbar notification to the page.\n *\n * @param {String} message\n * @param {Object} configuration\n * @param {String} [configuration.title]\n * @param {String} [configuration.subtitle]\n * @param {Bool} [configuration.autohide=true]\n * @param {Number} [configuration.delay=4000]\n */\nexport const add = async(message, configuration) => {\n const pendingPromise = new Pending('addToastRegion');\n configuration = {\n closeButton: false,\n autohide: true,\n delay: 4000,\n ...configuration,\n };\n\n const templateName = `core/local/toast/message`;\n try {\n const targetNode = await getTargetNode();\n const {html, js} = await Templates.renderForPromise(templateName, {\n message,\n ...configuration\n });\n Templates.prependNodeContents(targetNode, html, js);\n } catch (e) {\n Notification.exception(e);\n }\n\n pendingPromise.resolve();\n};\n\nconst getTargetNode = async() => {\n const region = document.querySelector('.toast-wrapper');\n\n if (region) {\n return region;\n }\n\n await addToastRegion(document.body, 'fixed-bottom');\n return getTargetNode();\n};\n"],"file":"toast.min.js"}

View File

@ -44,7 +44,7 @@ define(['jquery', 'core/templates'], function($, Templates) {
return getIcon()
.then(function(html) {
var loadingIcon = $(html).hide();
container.append(loadingIcon);
$(container).append(loadingIcon);
loadingIcon.fadeIn(150);
return loadingIcon;
@ -65,7 +65,7 @@ define(['jquery', 'core/templates'], function($, Templates) {
return getIcon()
.then(function(html) {
var loadingIcon = $(html).hide();
container.append(loadingIcon);
$(container).append(loadingIcon);
loadingIcon.fadeIn(150);
return $.when(loadingIcon.promise(), loadingIconPromise);

View File

@ -722,7 +722,7 @@ define([
* content with the their respective translated dates.
*
* @param {String} content The content in which string placeholders are to be found.
* @param {Array} strings The strings to replace with.
* @param {Array} dates The dates to replace with.
* @return {String} The treated content.
*/
Renderer.prototype.treatDatesInContent = function(content, dates) {
@ -1045,6 +1045,33 @@ define([
return renderer.render(templateName, context, themeName);
},
/**
* Every call to render creates a new instance of the class and calls render on it. This
* means each render call has it's own class variables.
*
* This alernate to the standard .render() function returns the html and js in a single object suitable for a
* native Promise.
*
* @method renderForPromise
* @private
* @param {string} templateName - should consist of the component and the name of the template like this:
* core/menu (lib/templates/menu.mustache) or
* tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)
* @param {Object} context - Could be array, string or simple value for the context of the template.
* @param {string} themeName - Name of the current theme.
* @return {Promise} JQuery promise object resolved when the template has been rendered.
*/
renderForPromise: function(templateName, context, themeName) {
var renderer = new Renderer();
return renderer.render(templateName, context, themeName)
.then(function(html, js) {
return {
html: html,
js: js,
};
});
},
/**
* Every call to renderIcon creates a new instance of the class and calls renderIcon on it. This
* means each render call has it's own class variables.
@ -1116,6 +1143,6 @@ define([
*/
appendNodeContents: function(element, html, js) {
domAppend(element, html, js);
}
},
};
});

89
lib/amd/src/toast.js Normal file
View File

@ -0,0 +1,89 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* A system for displaying small snackbar notifications to users which disappear shortly after they are shown.
*
* @module core/toast
* @package core
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Templates from 'core/templates';
import Notification from 'core/notification';
import Pending from 'core/pending';
/**
* Add a new region to place toasts in, taking in a parent element.
*
* @param {Element} parent
*/
export const addToastRegion = async(parent) => {
const pendingPromise = new Pending('addToastRegion');
try {
const {html, js} = await Templates.renderForPromise('core/local/toast/wrapper', {});
Templates.prependNodeContents(parent, html, js);
} catch (e) {
Notification.exception(e);
}
pendingPromise.resolve();
};
/**
* Add a new toast or snackbar notification to the page.
*
* @param {String} message
* @param {Object} configuration
* @param {String} [configuration.title]
* @param {String} [configuration.subtitle]
* @param {Bool} [configuration.autohide=true]
* @param {Number} [configuration.delay=4000]
*/
export const add = async(message, configuration) => {
const pendingPromise = new Pending('addToastRegion');
configuration = {
closeButton: false,
autohide: true,
delay: 4000,
...configuration,
};
const templateName = `core/local/toast/message`;
try {
const targetNode = await getTargetNode();
const {html, js} = await Templates.renderForPromise(templateName, {
message,
...configuration
});
Templates.prependNodeContents(targetNode, html, js);
} catch (e) {
Notification.exception(e);
}
pendingPromise.resolve();
};
const getTargetNode = async() => {
const region = document.querySelector('.toast-wrapper');
if (region) {
return region;
}
await addToastRegion(document.body, 'fixed-bottom');
return getTargetNode();
};

View File

@ -613,6 +613,14 @@ $functions = array(
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_course_get_enrolled_users_by_cmid' => array(
'classname' => 'core_course_external',
'methodname' => 'get_enrolled_users_by_cmid',
'classpath' => 'course/externallib.php',
'description' => 'List users bycourse module id.',
'type' => 'read',
'ajax' => true,
),
'core_enrol_get_course_enrolment_methods' => array(
'classname' => 'core_enrol_external',
'methodname' => 'get_course_enrolment_methods',
@ -802,6 +810,40 @@ $functions = array(
'description' => 'Update a grade item and associated student grades.',
'type' => 'write',
),
'core_grades_grader_gradingpanel_point_fetch' => [
'classname' => 'core_grades\\grades\\grader\\gradingpanel\\point\\external\\fetch',
'methodname' => 'execute',
'description' => 'Fetch the data required to display the grader grading panel for simple grading, ' .
'creating the grade item if required',
'type' => 'write',
'ajax' => true,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'core_grades_grader_gradingpanel_point_store' => [
'classname' => 'core_grades\\grades\\grader\\gradingpanel\\point\\external\\store',
'methodname' => 'execute',
'description' => 'Store the data required to display the grader grading panel for simple grading',
'type' => 'write',
'ajax' => true,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'core_grades_grader_gradingpanel_scale_fetch' => [
'classname' => 'core_grades\\grades\\grader\\gradingpanel\\scale\\external\\fetch',
'methodname' => 'execute',
'description' => 'Fetch the data required to display the grader grading panel for scale-based grading, ' .
'creating the grade item if required',
'type' => 'write',
'ajax' => true,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'core_grades_grader_gradingpanel_scale_store' => [
'classname' => 'core_grades\\grades\\grader\\gradingpanel\\scale\\external\\store',
'methodname' => 'execute',
'description' => 'Store the data required to display the grader grading panel for scale-based grading',
'type' => 'write',
'ajax' => true,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'core_grading_get_definitions' => array(
'classname' => 'core_grading_external',
'methodname' => 'get_definitions',

View File

@ -510,7 +510,7 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group {
}
} else {
$this->gradetypeformelement->setValue('none');
$this->maxgradeformelement->setValue('');
$this->maxgradeformelement->setValue(100);
}
}
break;

View File

@ -186,7 +186,7 @@ Feature: Using the activity grade form element
And I navigate to "Edit settings" in current page administration
When I expand all fieldsets
Then I should see "You cannot change the type, as grades already exist for this item."
And I set the field "Maximum grade" to "50"
And I set the field "Ratings > Maximum grade" to "50"
And I press "Save and display"
And I should see "You cannot change the maximum grade when grades already exist for an activity with ratings"

View File

@ -24,6 +24,8 @@
defined('MOODLE_INTERNAL') || die();
global $CFG;
/** Include essential files */
require_once($CFG->libdir . '/grade/constants.php');

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@template core/local/toast/message
Template to render a Toast notification.
Classes required for JS:
* none
Data attributes required for JS:
* none
Example context (json):
{
"data": 200,
"autohide": true,
"Message": "Cubing negative numbers is tough",
"closebutton": true
}
}}
<div id="toast-{{uniqid}}" class="toast bg-dark text-white rounded mx-auto" data-delay="{{delay}}" {{^autohide}}data-autohide="false"{{/autohide}}>
<div class="toast-body">
<span>{{{message}}}</span>
{{#closeButton}}
<button type="button" class="ml-2 mb-1 close text-white" data-dismiss="toast" aria-label="{{#str}}dismissnotification, core{{/str}}">
<span aria-hidden="true">&times;</span>
</button>
{{/closeButton}}
</div>
</div>
{{#js}}
require(['jquery', 'theme_boost/toast'], function(jQuery) {
// Show the toast.
// Bootstrap toast components are not shown automatically.
jQuery('#toast-{{uniqid}}').toast('show');
jQuery('#toast-{{uniqid}}').on('hidden.bs.toast', function(e) {
e.target.remove();
});
});
{{/js}}

View File

@ -0,0 +1 @@
<div class="toast-wrapper mx-auto py-3 fixed-top" role="status" aria-live="polite"></div>

View File

@ -209,7 +209,7 @@ Y.extend(DIALOGUE, Y.Panel, {
* @method applyZIndex
*/
applyZIndex: function() {
var highestzindex = 1,
var highestzindex = 1040,
zindexvalue = 1,
bb = this.get('boundingBox'),
ol = this.get('maskNode'),

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More