From d35499312dd82b5f3a207490daf9539553c2e5f7 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Mon, 17 Dec 2012 11:13:10 +0800 Subject: [PATCH 1/3] MDL-30085 core_grade: added a grade web service --- course/externallib.php | 1 + grade/upgrade.txt | 8 + lib/db/services.php | 19 ++ lib/grade/externallib.php | 448 +++++++++++++++++++++++++ lib/grade/tests/externallib_test.php | 477 +++++++++++++++++++++++++++ lib/gradelib.php | 24 +- 6 files changed, 974 insertions(+), 3 deletions(-) create mode 100644 grade/upgrade.txt create mode 100644 lib/grade/externallib.php create mode 100644 lib/grade/tests/externallib_test.php diff --git a/course/externallib.php b/course/externallib.php index f8b2fa4a703..6c10da1a5f9 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -140,6 +140,7 @@ class core_course_external extends external_api { //common info (for people being able to see the module or availability dates) $module['id'] = $cm->id; $module['name'] = format_string($cm->name, true); + $module['instance'] = $cm->instance; $module['modname'] = $cm->modname; $module['modplural'] = $cm->modplural; $module['modicon'] = $cm->get_icon_url()->out(false); diff --git a/grade/upgrade.txt b/grade/upgrade.txt new file mode 100644 index 00000000000..a313a200a54 --- /dev/null +++ b/grade/upgrade.txt @@ -0,0 +1,8 @@ +This files describes API changes in /grade/*, +information provided here is intended especially for developers. + + +=== 2.7.0 === +* The class core_grade_external in grade/externallib.php has been renamed to core_grading_external. + grade/externallib.php is concerned with the grading API not the grades API. + The change was necessary to avoid a duplicate class name with core_grade_external in lib/grade/externallib.php. diff --git a/lib/db/services.php b/lib/db/services.php index 7d9f557ac1b..1078f1e04a8 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -99,6 +99,25 @@ $functions = array( 'type' => 'read', 'capabilities'=> 'moodle/cohort:view', ), + // === grade related functions === + + 'core_grade_get_grades' => array( + 'classname' => 'core_grade_external', + 'methodname' => 'get_grades', + 'classpath' => 'lib/grade/externallib.php', + 'description' => 'Returns grade item details and optionally student grades.', + 'type' => 'read', + 'capabilities'=> 'moodle/grade:view, moodle/grade:viewall', + ), + + 'core_grade_update_grades' => array( + 'classname' => 'core_grade_external', + 'methodname' => 'update_grades', + 'classpath' => 'lib/grade/externallib.php', + 'description' => 'Update a grade item and associated student grades.', + 'type' => 'write', + 'capabilities'=> '', + ), // === group related functions === diff --git a/lib/grade/externallib.php b/lib/grade/externallib.php new file mode 100644 index 00000000000..7471ed2a124 --- /dev/null +++ b/lib/grade/externallib.php @@ -0,0 +1,448 @@ +. + + +/** + * External grade API + * + * @package core_grade + * @category external + * @copyright 2012 Andrew Davis + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once("$CFG->libdir/externallib.php"); + +/** + * Grade external functions + * + * @package core_grade + * @category external + * @copyright 2012 Andrew Davis + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.6 + */ +class core_grade_external extends external_api { + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 2.6 + */ + public static function get_grades_parameters() { + return new external_function_parameters( + array( + 'courseid' => new external_value(PARAM_INT, 'id of course'), + 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''), + 'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null), + 'userids' => new external_multiple_structure( + new external_value(PARAM_INT, 'user ID'), 'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array() + ) + ) + ); + } + + /** + * Retrieve grade items and, optionally, student grades + * + * @param array $grades array of grade information + * @return array of newly created groups + * @since Moodle 2.6 + */ + public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) { + global $CFG, $USER, $DB; + require_once("$CFG->libdir/gradelib.php"); + + $params = self::validate_parameters(self::get_grades_parameters(), + array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids)); + + $coursecontext = context_course::instance($params['courseid']); + + try { + self::validate_context($coursecontext); + } catch (Exception $e) { + $exceptionparam = new stdClass(); + $exceptionparam->message = $e->getMessage(); + $exceptionparam->courseid = $params['courseid']; + throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); + } + + $course = $DB->get_record('course', array('id' => $params['courseid'])); + + $access = false; + if (has_capability('moodle/grade:viewall', $coursecontext)) { + // Can view all user's grades in this course. + $access = true; + + } else if ($course->showgrades && count($params['userids']) == 1) { + // Course showgrades == students/parents can access grades. + + if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) { + // Student can view their own grades in this course. + $access = true; + + } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) { + // User can view the grades of this user. Parent most probably. + $access = true; + } + } + + if (!$access) { + throw new moodle_exception('nopermissiontoviewgrades', 'error'); + } + + $itemtype = null; + $itemmodule = null; + if (!empty($params['component'])) { + list($itemtype, $itemmodule) = normalize_component($params['component']); + } + + $cm = null; + if (!empty($itemmodule) && !empty($activityid)) { + if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { + throw new moodle_exception('invalidcoursemodule'); + } + } + + $cminstanceid = null; + if (!empty($cm)) { + $cminstanceid = $cm->instance; + } + $grades = grade_get_grades($params['courseid'], $itemtype, $itemmodule, $cminstanceid, $params['userids']); + + $activity_instances = null; + if (empty($cm)) { + // If we're dealing with multiple activites load all the module info. + $modinfo = get_fast_modinfo($params['courseid']); + $activity_instances = $modinfo->get_instances(); + } + + foreach ($grades->items as $gradeitem) { + if (!empty($cm)) { + // If they only requested one activity we will already have the cm. + $modulecm = $cm; + } else if (!empty($gradeitem->itemmodule)) { + $modulecm = $activity_instances[$gradeitem->itemmodule][$gradeitem->iteminstance]; + } else { + // Course grade item. + continue; + } + + // Make student feedback ready for output. + foreach ($gradeitem->grades as $studentgrade) { + if (!empty($studentgrade->feedback)) { + list($studentgrade->feedback, $categoryinfo->feedbackformat) = + external_format_text($studentgrade->feedback, $studentgrade->feedbackformat, + $modulecm->id, $params['component'], 'feedback', null); + } + } + } + + // Convert from objects to arrays so all web service clients are supported. + // While we're doing that we also remove grades the current user can't see due to hiding + $grades_array = array(); + $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid'])); + + $grades_array['items'] = array(); + foreach ($grades->items as $grade_item) { + // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID. + $grade_item_instance = self::get_grade_item($course->id, $grade_item->itemtype, $grade_item->itemmodule, $grade_item->iteminstance, 0); + if (!$canviewhidden && $grade_item_instance->is_hidden()) { + continue; + } + $grade_item_array = (array)$grade_item; + $grade_item_array['grades'] = array(); + + if (!empty($grade_item->grades)) { + foreach ($grade_item->grades as $studentid => $studentgrade) { + $grade_grade_instance = grade_grade::fetch( + array( + 'userid' => $studentid, + 'itemid' => $grade_item_instance->id + ) + ); + if (!$canviewhidden && $grade_grade_instance->is_hidden()) { + continue; + } + $grade_item_array['grades'][$studentid] = (array)$studentgrade; + // Add the student ID as some WS clients can't access the array key. + $grade_item_array['grades'][$studentid]['userid'] = $studentid; + } + } + + // If they requested grades for multiple activities load the cm object now. + $modulecm = $cm; + if (empty($modulecm) && !empty($grade_item_instance->itemmodule)) { + $modulecm = $activity_instances[$grade_item_instance->itemmodule][$grade_item_instance->iteminstance]; + } + if ($grade_item_instance->itemtype == 'course') { + $grades_array['items']['course'] = $grade_item_array; + $grades_array['items']['course']['activityid'] = 'course'; + } else { + $grades_array['items'][$modulecm->id] = $grade_item_array; + // Add the activity ID as some WS clients can't access the array key. + $grades_array['items'][$modulecm->id]['activityid'] = $modulecm->id; + } + } + + $grades_array['outcomes'] = array(); + foreach ($grades->outcomes as $outcome) { + $modulecm = $cm; + if (empty($modulecm)) { + $modulecm = $activity_instances[$outcome->itemmodule][$outcome->iteminstance]; + } + $grades_array['outcomes'][$modulecm->id] = (array)$outcome; + $grades_array['outcomes'][$modulecm->id]['activityid'] = $modulecm->id; + + $grades_array['outcomes'][$modulecm->id]['grades'] = array(); + if (!empty($outcome->grades)) { + foreach ($outcome->grades as $studentid => $studentgrade) { + if (!$canviewhidden) { + // Need to load the grade_grade object to check visibility. + $grade_item_instance = self::get_grade_item($course->id, $outcome->itemtype, $outcome->itemmodule, $outcome->iteminstance, $outcome->itemnumber); + $grade_grade_instance = grade_grade::fetch( + array( + 'userid' => $studentid, + 'itemid' => $grade_item_instance->id + ) + ); + // The grade grade may be legitimately missing if the student has no grade. + if (!empty($grade_grade_instance) && $grade_grade_instance->is_hidden()) { + continue; + } + } + $grades_array['outcomes'][$modulecm->id]['grades'][$studentid] = (array)$studentgrade; + + // Add the student ID into the grade structure as some WS clients can't access the key. + $grades_array['outcomes'][$modulecm->id]['grades'][$studentid]['userid'] = $studentid; + } + } + } + + return $grades_array; + } + + private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) { + $grade_item_instance = null; + if ($itemtype == 'course') { + $grade_item_instance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype)); + } else { + $grade_item_instance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype, 'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber)); + } + return $grade_item_instance; + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 2.6 + */ + public static function get_grades_returns() { + return new external_single_structure( + array( + 'items' => new external_multiple_structure( + new external_single_structure( + array( + 'activityid' => new external_value(PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), + 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), + 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), + 'name' => new external_value(PARAM_RAW, 'The module name'), + 'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'), + 'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'), + 'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'), + 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), + 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'userid' => new external_value(PARAM_INT, 'Student ID'), + 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), + 'locked' => new external_value(PARAM_BOOL, 'Is the student\'s grade locked?'), + 'hidden' => new external_value(PARAM_BOOL, 'Is the student\'s grade hidden?'), + 'overridden' => new external_value(PARAM_BOOL, 'Is the student\'s grade overridden?'), + 'feedback' => new external_value(PARAM_RAW, 'Feedback from the grader'), + 'feedbackformat' => new external_value(PARAM_INT, 'The format of the feedback'), + 'usermodified' => new external_value(PARAM_INT, 'The ID of the last user to modify this student grade'), + 'datesubmitted' => new external_value(PARAM_INT, 'A timestamp indicating when the student submitted the activity'), + 'dategraded' => new external_value(PARAM_INT, 'A timestamp indicating when the assignment was grades'), + 'str_grade' => new external_value(PARAM_RAW, 'A string representation of the grade'), + 'str_long_grade' => new external_value(PARAM_RAW, 'A nicely formatted string representation of the grade'), + 'str_feedback' => new external_value(PARAM_TEXT, 'A string representation of the feedback from the grader'), + ) + ) + ), + ) + ) + ), + 'outcomes' => new external_multiple_structure( + new external_single_structure( + array( + 'activityid' => new external_value(PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), + 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), + 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), + 'name' => new external_value(PARAM_RAW, 'The module name'), + 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), + 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'userid' => new external_value(PARAM_INT, 'Student ID'), + 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), + 'locked' => new external_value(PARAM_BOOL, 'Is the student\'s grade locked?'), + 'hidden' => new external_value(PARAM_BOOL, 'Is the student\'s grade hidden?'), + 'feedback' => new external_value(PARAM_RAW, 'Feedback from the grader'), + 'feedbackformat' => new external_value(PARAM_INT, 'The feedback format'), + 'usermodified' => new external_value(PARAM_INT, 'The ID of the last user to modify this student grade'), + 'str_grade' => new external_value(PARAM_RAW, 'A string representation of the grade'), + 'str_feedback' => new external_value(PARAM_TEXT, 'A string representation of the feedback from the grader'), + ) + ) + ), + ) + ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL + ) + ) + ); + + } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 2.6 + */ + public static function update_grades_parameters() { + return new external_function_parameters( + array( + 'source' => new external_value(PARAM_TEXT, 'The source of the grade update'), + 'courseid' => new external_value(PARAM_INT, 'id of course'), + 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'), + 'activityid' => new external_value(PARAM_INT, 'The activity ID'), + 'itemnumber' => new external_value(PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'studentid' => new external_value(PARAM_INT, 'Student ID'), + 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), + 'str_feedback' => new external_value(PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL), + ) + ), 'Any student grades to alter', VALUE_OPTIONAL), + 'itemdetails' => new external_single_structure( + array( + 'itemname' => new external_value(PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL), + 'idnumber' => new external_value(PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL), + 'gradetype' => new external_value(PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL), + 'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL), + 'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL), + 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL), + 'multfactor' => new external_value(PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL), + 'plusfactor' => new external_value(PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL), + 'deleted' => new external_value(PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL), + 'hidden' => new external_value(PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL), + ), 'Any grade item settings to alter', VALUE_OPTIONAL + ) + ) + ); + } + + /** + * Update a grade item and, optionally, student grades + * + * @param array $grade array of grade information + * @since Moodle 2.6 + */ + public static function update_grades($source, $courseid, $component, $activityid, $itemnumber, $grades = array(), $itemdetails = array()) { + global $CFG; + + require_once("$CFG->libdir/gradelib.php"); + + $params = self::validate_parameters( + self::update_grades_parameters(), + array( + 'source' => $source, + 'courseid' => $courseid, + 'component' => $component, + 'activityid' => $activityid, + 'itemnumber' => $itemnumber, + 'grades' => $grades, + 'itemdetails' => $itemdetails + ) + ); + + list($itemtype, $itemmodule) = normalize_component($params['component']); + + if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { + throw new moodle_exception('invalidcoursemodule'); + } + $iteminstance = $cm->instance; + + $coursecontext = context_course::instance($params['courseid']); + + try { + self::validate_context($coursecontext); + } catch (Exception $e) { + $exceptionparam = new stdClass(); + $exceptionparam->message = $e->getMessage(); + $exceptionparam->courseid = $params['courseid']; + throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); + } + + $hidinggrades = false; + $editinggradeitem = false; + $editinggrades = false; + + $gradestructure = array(); + foreach ($grades as $grade) { + $editinggrades = true; + $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']); + } + if (!empty($params['itemdetails'])) { + if (isset($params['itemdetails']['hidden'])) { + $hidinggrades = true; + } else { + $editinggradeitem = true; + } + } + + if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, 'moodle/grade:manage required to edit grade information'); + } + if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) && !has_capability('moodle/grade:hide', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, 'moodle/grade:hide required to hide grade items'); + } + if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, 'moodle/grade:edit required to edit grades'); + } + + return grade_update($params['source'], $params['courseid'], $itemtype, $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']); + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 2.6 + */ + public static function update_grades_returns() { + return new external_single_structure( + array ( + 'result' => new external_value(PARAM_INT, 'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED as defined in lib/grade/constants.php') + ) + ); + } +} diff --git a/lib/grade/tests/externallib_test.php b/lib/grade/tests/externallib_test.php new file mode 100644 index 00000000000..40a24d2fa95 --- /dev/null +++ b/lib/grade/tests/externallib_test.php @@ -0,0 +1,477 @@ +. + +/** + * Unit tests for the grade API at /lib/grade/externallib.php + * + * @package core_grade + * @category external + * @copyright 2012 Andrew Davis + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.6 + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); +require_once($CFG->libdir . '/grade/externallib.php'); + +class core_grade_external_testcase extends externallib_advanced_testcase { + + protected function load_test_data($assignmentname, $student1rawgrade, $student2rawgrade) { + global $DB; + + // Adds a course, a teacher, 2 students, an assignment and grades for the students. + $course = $this->getDataGenerator()->create_course(); + $coursecontext = context_course::instance($course->id); + + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + + $student1 = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id); + + $student2 = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id); + + $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher')); + $teacher = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); + + $parent = $this->getDataGenerator()->create_user(); + $this->setUser($parent); + $student1context = context_user::instance($student1->id); + // Creates a new role, gives it the capability and gives $USER that role. + $parentroleid = $this->assignUserCapability('moodle/grade:viewall', $student1context->id); + // Enrol the user in the course using the new role. + $this->getDataGenerator()->enrol_user($parent->id, $course->id, $parentroleid); + + $assignment = $this->getDataGenerator()->create_module('assign', array('name' => $assignmentname, 'course' => $course->id)); + $modcontext = get_coursemodule_from_instance('assign', $assignment->id, $course->id); + $assignment->cmidnumber = $modcontext->id; + + $student1grade = array('userid' => $student1->id, 'rawgrade' => $student1rawgrade); + $student2grade = array('userid' => $student2->id, 'rawgrade' => $student2rawgrade); + $studentgrades = array($student1->id => $student1grade, $student2->id => $student2grade); + assign_grade_item_update($assignment, $studentgrades); + + // Insert a custom grade scale to be used by an outcome. + $grade_scale = new grade_scale(); + $grade_scale->name = 'unittestscale3'; + $grade_scale->courseid = $course->id; + $grade_scale->userid = 0; + $grade_scale->scale = 'Distinction, Very Good, Good, Pass, Fail'; + $grade_scale->description = 'This scale is used to mark standard assignments.'; + $grade_scale->insert(); + + // Insert an outcome. + $data = new stdClass(); + $data->courseid = $course->id; + $data->fullname = 'Team work'; + $data->shortname = 'Team work'; + $data->scaleid = $grade_scale->id; + $outcome = new grade_outcome($data, false); + $outcome->insert(); + + $outcome_gradeitem = new grade_item(); + $outcome_gradeitem->itemname = $outcome->shortname; + $outcome_gradeitem->itemtype = 'mod'; + $outcome_gradeitem->itemmodule = 'assign'; + $outcome_gradeitem->iteminstance = $assignment->id; + $outcome_gradeitem->outcomeid = $outcome->id; + $outcome_gradeitem->cmid = 0; + $outcome_gradeitem->courseid = $course->id; + $outcome_gradeitem->aggregationcoef = 0; + $outcome_gradeitem->itemnumber = 1; // The activity's original grade item will be 0. + $outcome_gradeitem->gradetype = GRADE_TYPE_SCALE; + $outcome_gradeitem->scaleid = $outcome->scaleid; + $outcome_gradeitem->insert(); + + $assignment_gradeitem = grade_item::fetch( + array( + 'itemtype' => 'mod', + 'itemmodule' => 'assign', + 'iteminstance' => $assignment->id, + 'itemnumber' => 0, + 'courseid' => $course->id + ) + ); + $outcome_gradeitem->set_parent($assignment_gradeitem->categoryid); + $outcome_gradeitem->move_after_sortorder($assignment_gradeitem->sortorder); + + return array($course, $assignment, $student1, $student2, $teacher, $parent); + } + + /** + * Test get_grades() + */ + public function test_get_grades() { + global $CFG; + + $this->resetAfterTest(true); + $CFG->enableoutcomes = 1; + + $assignmentname = 'The assignment'; + $student1rawgrade = 10; + $student2rawgrade = 20; + list($course, $assignment, $student1, $student2, $teacher, $parent) = $this->load_test_data($assignmentname, $student1rawgrade, $student2rawgrade); + $assigment_cm = get_coursemodule_from_id('assign', $assignment->id, 0, false, MUST_EXIST); + + // Student requesting their own grade for the assignment. + $this->setUser($student1); + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array($student1->id) + ); + $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + + // Student requesting all of their grades in a course. + $grades = core_grade_external::get_grades( + $course->id, + null, + null, + array($student1->id) + ); + $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $this->assertTrue(count($grades['items']) == 2); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, 'course', $student1->id)); + + $outcome = $this->get_outcome($grades, $assigment_cm->id); + $this->assertEquals($outcome['name'], 'Team work'); + $this->assertEquals(0, $this->get_outcome_student_grade($grades, $assigment_cm->id, $student1->id)); + + // Student requesting another student's grade for the assignment (should fail). + try { + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array($student2->id) + ); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // Parent requesting their child's grade for the assignment. + $this->setUser($parent); + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array($student1->id) + ); + $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + + // Parent requesting another student's grade for the assignment(should fail). + try { + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array($student2->id) + ); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // Student requesting all other student grades for the assignment (should fail). + try { + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array($student1->id, $student2->id) + ); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // Student requesting only grade item information (should fail). + try { + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array() + ); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // Teacher requesting student grades for a course. + $this->setUser($teacher); + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array($student1->id, $student2->id) + ); + $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + $this->assertEquals($student2rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student2->id)); + + // Teacher requesting grade item information. + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id + ); + $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $activity = $this->get_activity($grades, $assigment_cm->id); + $this->assertEquals($activity['name'], $assignmentname); + $this->assertEquals(count($activity['grades']), 0); + + // Teacher requesting all grade items in a course. + $grades = core_grade_external::get_grades( + $course->id + ); + $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $this->assertTrue(count($grades['items']) == 2); + + $activity = $this->get_activity($grades, $assigment_cm->id); + $this->assertEquals($activity['name'], $assignmentname); + $this->assertEquals(count($activity['grades']), 0); + + $outcome = $this->get_outcome($grades, $assigment_cm->id); + $this->assertEquals($outcome['name'], 'Team work'); + + // Hide a grade item then have student request it + $result = core_grade_external::update_grades( + 'test', + $course->id, + 'mod_assign', + $assigment_cm->id, + 0, + array(), + array('hidden' => 1) + ); + $this->assertTrue($result == GRADE_UPDATE_OK); + + // Check it's definitely hidden. + $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); + $this->assertEquals($grades->items[0]->hidden, 1); + + // Student should now not be able to see it. + $this->setUser($student1); + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array($student1->id) + ); + $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $this->assertEquals(null, $this->get_activity($grades, $assigment_cm->id)); + + // Teacher should still be able to see the hidden grades. + $this->setUser($teacher); + $grades = core_grade_external::get_grades( + $course->id, + 'mod_assign', + $assigment_cm->id, + array($student1->id) + ); + $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + } + + private function get_activity($grades, $cmid) { + foreach ($grades['items'] as $item) { + if ($item['activityid'] == $cmid) { + return $item; + } + } + return null; + } + + private function get_activity_student_grade($grades, $cmid, $studentid) { + $item = $this->get_activity($grades, $cmid); + foreach ($item['grades'] as $grade) { + if ($grade['userid'] == $studentid) { + return $grade['grade']; + } + } + return null; + } + + private function get_outcome($grades, $cmid) { + foreach($grades['outcomes'] as $outcome) { + if ($outcome['activityid'] == $cmid) { + return $outcome; + } + } + return null; + } + + private function get_outcome_student_grade($grades, $cmid, $studentid) { + $outcome = $this->get_outcome($grades, $cmid); + foreach ($outcome['grades'] as $grade) { + if ($grade['userid'] == $studentid) { + return $grade['grade']; + } + } + return null; + } + + /** + * Test get_grades() + */ + public function test_update_grades() { + global $DB; + + $this->resetAfterTest(true); + + $assignmentname = 'The assignment'; + $student1rawgrade = 10; + $student2rawgrade = 20; + list($course, $assignment, $student1, $student2, $teacher, $parent) = $this->load_test_data($assignmentname, $student1rawgrade, $student2rawgrade); + $assigment_cm = get_coursemodule_from_id('assign', $assignment->id, 0, false, MUST_EXIST); + + $this->setUser($teacher); + + // Teacher updating grade item information + $changedmax = 93; + $result = core_grade_external::update_grades( + 'test', + $course->id, + 'mod_assign', + $assigment_cm->id, + 0, + array(), + array('grademax' => $changedmax) + ); + $this->assertTrue($result == GRADE_UPDATE_OK); + $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); + $this->assertTrue($grades->items[0]->grademax == $changedmax); + + // Teacher updating 1 student grade + $student1grade = 23; + $result = core_grade_external::update_grades( + 'test', + $course->id, + 'mod_assign', + $assigment_cm->id, + 0, + array(array('studentid' => $student1->id, 'grade' => $student1grade)) + ); + $this->assertTrue($result == GRADE_UPDATE_OK); + $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id, array($student1->id)); + $this->assertTrue($grades->items[0]->grades[$student1->id]->grade == $student1grade); + + // Teacher updating multiple student grades + $student1grade = 11; + $student2grade = 13; + $result = core_grade_external::update_grades( + 'test', + $course->id, + 'mod_assign', + $assigment_cm->id, + 0, + array( + array('studentid' => $student1->id, 'grade' => $student1grade), + array('studentid' => $student2->id, 'grade' => $student2grade) + ) + ); + $this->assertTrue($result == GRADE_UPDATE_OK); + $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id, array($student1->id, $student2->id)); + $this->assertTrue($grades->items[0]->grades[$student1->id]->grade == $student1grade); + $this->assertTrue($grades->items[0]->grades[$student2->id]->grade == $student2grade); + + // Student attempting to update their own grade (should fail) + $this->setUser($student1); + try { + $student1grade = 17; + $result = core_grade_external::update_grades( + 'test', + $course->id, + 'mod_assign', + $assigment_cm->id, + 0, + array( array('studentid' => $student1->id, 'grade' => $student1grade)) + ); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // Parent attempting to update their child's grade (should fail) + $this->setUser($parent); + try { + $student1grade = 13; + $result = core_grade_external::update_grades( + 'test', + $course->id, + 'mod_assign', + $assigment_cm->id, + 0, + array( array('studentid' => $student1->id, 'grade' => $student1grade)) + ); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // Student trying to hide a grade item (should fail). + $this->setUser($student1); + try { + $result = core_grade_external::update_grades( + 'test', + $course->id, + 'mod_assign', + $assigment_cm->id, + 0, + array(), + array('hidden' => 1) + ); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // Give the student role 'moodle/grade:hide' and they should now be able to hide the grade item. + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $coursecontext = context_course::instance($course->id); + assign_capability('moodle/grade:hide', CAP_ALLOW, $studentrole->id, $coursecontext->id); + accesslib_clear_all_caches_for_unit_testing(); + + // Check the activity isn't already hidden. + $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); + $this->assertTrue($grades->items[0]->hidden == 0); + + $result = core_grade_external::update_grades( + 'test', + $course->id, + 'mod_assign', + $assigment_cm->id, + 0, + array(), + array('hidden' => 1) + ); + $this->assertTrue($result == GRADE_UPDATE_OK); + $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); + $this->assertTrue($grades->items[0]->hidden == 1); + } + +} diff --git a/lib/gradelib.php b/lib/gradelib.php index 28bb4a70a1e..7cda902f28a 100644 --- a/lib/gradelib.php +++ b/lib/gradelib.php @@ -326,7 +326,7 @@ function grade_update_outcomes($source, $courseid, $itemtype, $itemmodule, $item } /** - * Returns grading information for given activity, optionally with user grades + * Returns grading information for one or more activities, optionally with user grades * Manual, course or category items can not be queried. * * @category grade @@ -337,7 +337,7 @@ function grade_update_outcomes($source, $courseid, $itemtype, $itemmodule, $item * @param mixed $userid_or_ids Either a single user ID, an array of user IDs or null. If user ID or IDs are not supplied returns information about grade_item * @return array Array of grade information objects (scaleid, name, grade and locked status, etc.) indexed with itemnumbers */ -function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) { +function grade_get_grades($courseid, $itemtype = null, $itemmodule = null, $iteminstance = null, $userid_or_ids=null) { global $CFG; $return = new stdClass(); @@ -353,14 +353,28 @@ function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $use } } - if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) { + $params = array('courseid'=>$courseid); + if (!empty($itemtype)) { + $params['itemtype'] = $itemtype; + } + if (!empty($itemmodule)) { + $params['itemmodule'] = $itemmodule; + } + if (!empty($iteminstance)) { + $params['iteminstance'] = $iteminstance; + } + if ($grade_items = grade_item::fetch_all($params)) { foreach ($grade_items as $grade_item) { $decimalpoints = null; if (empty($grade_item->outcomeid)) { // prepare information about grade item $item = new stdClass(); + $item->id = $grade_item->id; $item->itemnumber = $grade_item->itemnumber; + $item->itemtype = $grade_item->itemtype; + $item->itemmodule = $grade_item->itemmodule; + $item->iteminstance = $grade_item->iteminstance; $item->scaleid = $grade_item->scaleid; $item->name = $grade_item->get_name(); $item->grademin = $grade_item->grademin; @@ -459,7 +473,11 @@ function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $use // outcome info $outcome = new stdClass(); + $outcome->id = $grade_item->id; $outcome->itemnumber = $grade_item->itemnumber; + $outcome->itemtype = $grade_item->itemtype; + $outcome->itemmodule = $grade_item->itemmodule; + $outcome->iteminstance = $grade_item->iteminstance; $outcome->scaleid = $grade_outcome->scaleid; $outcome->name = $grade_outcome->get_name(); $outcome->locked = $grade_item->is_locked(); From 198e802f9deaf17ce9425b46a591127e9e8506bd Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Fri, 21 Feb 2014 16:09:00 +0100 Subject: [PATCH 2/3] MDL-30085 core_grades functions moved to correct location --- grade/externallib.php | 491 ++++++++++++++++++ .../tests/externallib_grades_test.php | 262 ++++++---- grade/upgrade.txt | 8 - lib/db/services.php | 30 +- lib/grade/externallib.php | 448 ---------------- lib/gradelib.php | 2 +- 6 files changed, 662 insertions(+), 579 deletions(-) rename lib/grade/tests/externallib_test.php => grade/tests/externallib_grades_test.php (65%) delete mode 100644 grade/upgrade.txt delete mode 100644 lib/grade/externallib.php diff --git a/grade/externallib.php b/grade/externallib.php index ef5e6988f7a..baa6506ab37 100644 --- a/grade/externallib.php +++ b/grade/externallib.php @@ -49,3 +49,494 @@ class core_grade_external extends external_api { } } + +/** + * Core grades external functions + * + * @package core_grades + * @category external + * @copyright 2012 Andrew Davis + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.7 + */ +class core_grades_external extends external_api { + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 2.7 + */ + public static function get_grades_parameters() { + return new external_function_parameters( + array( + 'courseid' => new external_value(PARAM_INT, 'id of course'), + 'component' => new external_value( + PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''), + 'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null), + 'userids' => new external_multiple_structure( + new external_value(PARAM_INT, 'user ID'), + 'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array() + ) + ) + ); + } + + /** + * Retrieve grade items and, optionally, student grades + * + * @param int $courseid Course id + * @param string $component Component name + * @param int $activityid Activity id + * @param array $userids Array of user ids + * @return array Array of grades + * @since Moodle 2.7 + */ + public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) { + global $CFG, $USER, $DB; + require_once("$CFG->libdir/gradelib.php"); + + $params = self::validate_parameters(self::get_grades_parameters(), + array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids)); + + $coursecontext = context_course::instance($params['courseid']); + + try { + self::validate_context($coursecontext); + } catch (Exception $e) { + $exceptionparam = new stdClass(); + $exceptionparam->message = $e->getMessage(); + $exceptionparam->courseid = $params['courseid']; + throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); + } + + $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST); + + $access = false; + if (has_capability('moodle/grade:viewall', $coursecontext)) { + // Can view all user's grades in this course. + $access = true; + + } else if ($course->showgrades && count($params['userids']) == 1) { + // Course showgrades == students/parents can access grades. + + if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) { + // Student can view their own grades in this course. + $access = true; + + } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) { + // User can view the grades of this user. Parent most probably. + $access = true; + } + } + + if (!$access) { + throw new moodle_exception('nopermissiontoviewgrades', 'error'); + } + + $itemtype = null; + $itemmodule = null; + if (!empty($params['component'])) { + list($itemtype, $itemmodule) = normalize_component($params['component']); + } + + $cm = null; + if (!empty($itemmodule) && !empty($activityid)) { + if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { + throw new moodle_exception('invalidcoursemodule'); + } + } + + $cminstanceid = null; + if (!empty($cm)) { + $cminstanceid = $cm->instance; + } + $grades = grade_get_grades($params['courseid'], $itemtype, $itemmodule, $cminstanceid, $params['userids']); + + $acitivityinstances = null; + if (empty($cm)) { + // If we're dealing with multiple activites load all the module info. + $modinfo = get_fast_modinfo($params['courseid']); + $acitivityinstances = $modinfo->get_instances(); + } + + foreach ($grades->items as $gradeitem) { + if (!empty($cm)) { + // If they only requested one activity we will already have the cm. + $modulecm = $cm; + } else if (!empty($gradeitem->itemmodule)) { + $modulecm = $acitivityinstances[$gradeitem->itemmodule][$gradeitem->iteminstance]; + } else { + // Course grade item. + continue; + } + + // Make student feedback ready for output. + foreach ($gradeitem->grades as $studentgrade) { + if (!empty($studentgrade->feedback)) { + list($studentgrade->feedback, $categoryinfo->feedbackformat) = + external_format_text($studentgrade->feedback, $studentgrade->feedbackformat, + $modulecm->id, $params['component'], 'feedback', null); + } + } + } + + // Convert from objects to arrays so all web service clients are supported. + // While we're doing that we also remove grades the current user can't see due to hiding. + $gradesarray = array(); + $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid'])); + + $gradesarray['items'] = array(); + foreach ($grades->items as $gradeitem) { + // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID. + $gradeiteminstance = self::get_grade_item( + $course->id, $gradeitem->itemtype, $gradeitem->itemmodule, $gradeitem->iteminstance, 0); + if (!$canviewhidden && $gradeiteminstance->is_hidden()) { + continue; + } + $gradeitemarray = (array)$gradeitem; + $gradeitemarray['grades'] = array(); + + if (!empty($gradeitem->grades)) { + foreach ($gradeitem->grades as $studentid => $studentgrade) { + $gradegradeinstance = grade_grade::fetch( + array( + 'userid' => $studentid, + 'itemid' => $gradeiteminstance->id + ) + ); + if (!$canviewhidden && $gradegradeinstance->is_hidden()) { + continue; + } + $gradeitemarray['grades'][$studentid] = (array)$studentgrade; + // Add the student ID as some WS clients can't access the array key. + $gradeitemarray['grades'][$studentid]['userid'] = $studentid; + } + } + + // If they requested grades for multiple activities load the cm object now. + $modulecm = $cm; + if (empty($modulecm) && !empty($gradeiteminstance->itemmodule)) { + $modulecm = $acitivityinstances[$gradeiteminstance->itemmodule][$gradeiteminstance->iteminstance]; + } + if ($gradeiteminstance->itemtype == 'course') { + $gradesarray['items']['course'] = $gradeitemarray; + $gradesarray['items']['course']['activityid'] = 'course'; + } else { + $gradesarray['items'][$modulecm->id] = $gradeitemarray; + // Add the activity ID as some WS clients can't access the array key. + $gradesarray['items'][$modulecm->id]['activityid'] = $modulecm->id; + } + } + + $gradesarray['outcomes'] = array(); + foreach ($grades->outcomes as $outcome) { + $modulecm = $cm; + if (empty($modulecm)) { + $modulecm = $acitivityinstances[$outcome->itemmodule][$outcome->iteminstance]; + } + $gradesarray['outcomes'][$modulecm->id] = (array)$outcome; + $gradesarray['outcomes'][$modulecm->id]['activityid'] = $modulecm->id; + + $gradesarray['outcomes'][$modulecm->id]['grades'] = array(); + if (!empty($outcome->grades)) { + foreach ($outcome->grades as $studentid => $studentgrade) { + if (!$canviewhidden) { + // Need to load the grade_grade object to check visibility. + $gradeiteminstance = self::get_grade_item( + $course->id, $outcome->itemtype, $outcome->itemmodule, $outcome->iteminstance, $outcome->itemnumber); + $gradegradeinstance = grade_grade::fetch( + array( + 'userid' => $studentid, + 'itemid' => $gradeiteminstance->id + ) + ); + // The grade grade may be legitimately missing if the student has no grade. + if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) { + continue; + } + } + $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid] = (array)$studentgrade; + + // Add the student ID into the grade structure as some WS clients can't access the key. + $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid]['userid'] = $studentid; + } + } + } + + return $gradesarray; + } + + /** + * Get a grade item + * @param int $courseid Course id + * @param string $itemtype Item type + * @param string $itemmodule Item module + * @param int $iteminstance Item instance + * @param int $itemnumber Item number + * @return grade_item A grade_item instance + */ + private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) { + $gradeiteminstance = null; + if ($itemtype == 'course') { + $gradeiteminstance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype)); + } else { + $gradeiteminstance = grade_item::fetch( + array('courseid' => $courseid, 'itemtype' => $itemtype, + 'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber)); + } + return $gradeiteminstance; + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 2.7 + */ + public static function get_grades_returns() { + return new external_single_structure( + array( + 'items' => new external_multiple_structure( + new external_single_structure( + array( + 'activityid' => new external_value( + PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), + 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), + 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), + 'name' => new external_value(PARAM_RAW, 'The module name'), + 'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'), + 'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'), + 'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'), + 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), + 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'userid' => new external_value( + PARAM_INT, 'Student ID'), + 'grade' => new external_value( + PARAM_FLOAT, 'Student grade'), + 'locked' => new external_value( + PARAM_BOOL, 'Is the student\'s grade locked?'), + 'hidden' => new external_value( + PARAM_BOOL, 'Is the student\'s grade hidden?'), + 'overridden' => new external_value( + PARAM_BOOL, 'Is the student\'s grade overridden?'), + 'feedback' => new external_value( + PARAM_RAW, 'Feedback from the grader'), + 'feedbackformat' => new external_value( + PARAM_INT, 'The format of the feedback'), + 'usermodified' => new external_value( + PARAM_INT, 'The ID of the last user to modify this student grade'), + 'datesubmitted' => new external_value( + PARAM_INT, 'A timestamp indicating when the student submitted the activity'), + 'dategraded' => new external_value( + PARAM_INT, 'A timestamp indicating when the assignment was grades'), + 'str_grade' => new external_value( + PARAM_RAW, 'A string representation of the grade'), + 'str_long_grade' => new external_value( + PARAM_RAW, 'A nicely formatted string representation of the grade'), + 'str_feedback' => new external_value( + PARAM_TEXT, 'A string representation of the feedback from the grader'), + ) + ) + ), + ) + ) + ), + 'outcomes' => new external_multiple_structure( + new external_single_structure( + array( + 'activityid' => new external_value( + PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), + 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), + 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), + 'name' => new external_value(PARAM_RAW, 'The module name'), + 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), + 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'userid' => new external_value( + PARAM_INT, 'Student ID'), + 'grade' => new external_value( + PARAM_FLOAT, 'Student grade'), + 'locked' => new external_value( + PARAM_BOOL, 'Is the student\'s grade locked?'), + 'hidden' => new external_value( + PARAM_BOOL, 'Is the student\'s grade hidden?'), + 'feedback' => new external_value( + PARAM_RAW, 'Feedback from the grader'), + 'feedbackformat' => new external_value( + PARAM_INT, 'The feedback format'), + 'usermodified' => new external_value( + PARAM_INT, 'The ID of the last user to modify this student grade'), + 'str_grade' => new external_value( + PARAM_RAW, 'A string representation of the grade'), + 'str_feedback' => new external_value( + PARAM_TEXT, 'A string representation of the feedback from the grader'), + ) + ) + ), + ) + ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL + ) + ) + ); + + } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 2.7 + */ + public static function update_grades_parameters() { + return new external_function_parameters( + array( + 'source' => new external_value(PARAM_TEXT, 'The source of the grade update'), + 'courseid' => new external_value(PARAM_INT, 'id of course'), + 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'), + 'activityid' => new external_value(PARAM_INT, 'The activity ID'), + 'itemnumber' => new external_value( + PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'studentid' => new external_value(PARAM_INT, 'Student ID'), + 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), + 'str_feedback' => new external_value( + PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL), + ) + ), 'Any student grades to alter', VALUE_OPTIONAL), + 'itemdetails' => new external_single_structure( + array( + 'itemname' => new external_value( + PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL), + 'idnumber' => new external_value( + PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL), + 'gradetype' => new external_value( + PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL), + 'grademax' => new external_value( + PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL), + 'grademin' => new external_value( + PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL), + 'scaleid' => new external_value( + PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL), + 'multfactor' => new external_value( + PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL), + 'plusfactor' => new external_value( + PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL), + 'deleted' => new external_value( + PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL), + 'hidden' => new external_value( + PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL), + ), 'Any grade item settings to alter', VALUE_OPTIONAL + ) + ) + ); + } + + /** + * Update a grade item and, optionally, student grades + * + * @param string $source The source of the grade update + * @param int $courseid The course id + * @param string $component Component name + * @param int $activityid The activity id + * @param int $itemnumber The item number + * @param array $grades Array of grades + * @param array $itemdetails Array of item details + * @return int A status flag + * @since Moodle 2.7 + */ + public static function update_grades($source, $courseid, $component, $activityid, + $itemnumber, $grades = array(), $itemdetails = array()) { + global $CFG; + + require_once("$CFG->libdir/gradelib.php"); + + $params = self::validate_parameters( + self::update_grades_parameters(), + array( + 'source' => $source, + 'courseid' => $courseid, + 'component' => $component, + 'activityid' => $activityid, + 'itemnumber' => $itemnumber, + 'grades' => $grades, + 'itemdetails' => $itemdetails + ) + ); + + list($itemtype, $itemmodule) = normalize_component($params['component']); + + if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { + throw new moodle_exception('invalidcoursemodule'); + } + $iteminstance = $cm->instance; + + $coursecontext = context_course::instance($params['courseid']); + + try { + self::validate_context($coursecontext); + } catch (Exception $e) { + $exceptionparam = new stdClass(); + $exceptionparam->message = $e->getMessage(); + $exceptionparam->courseid = $params['courseid']; + throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); + } + + $hidinggrades = false; + $editinggradeitem = false; + $editinggrades = false; + + $gradestructure = array(); + foreach ($grades as $grade) { + $editinggrades = true; + $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']); + } + if (!empty($params['itemdetails'])) { + if (isset($params['itemdetails']['hidden'])) { + $hidinggrades = true; + } else { + $editinggradeitem = true; + } + } + + if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, + 'moodle/grade:manage required to edit grade information'); + } + if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) && + !has_capability('moodle/grade:hide', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, + 'moodle/grade:hide required to hide grade items'); + } + if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, + 'moodle/grade:edit required to edit grades'); + } + + return grade_update($params['source'], $params['courseid'], $itemtype, + $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']); + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 2.7 + */ + public static function update_grades_returns() { + return new external_single_structure( + array ( + 'result' => new external_value( + PARAM_INT, + 'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED + as defined in lib/grade/constants.php') + ) + ); + } +} diff --git a/lib/grade/tests/externallib_test.php b/grade/tests/externallib_grades_test.php similarity index 65% rename from lib/grade/tests/externallib_test.php rename to grade/tests/externallib_grades_test.php index 40a24d2fa95..72663e6a5eb 100644 --- a/lib/grade/tests/externallib_test.php +++ b/grade/tests/externallib_grades_test.php @@ -15,13 +15,13 @@ // along with Moodle. If not, see . /** - * Unit tests for the grade API at /lib/grade/externallib.php + * Unit tests for the grade API at /grade/externallib.php * - * @package core_grade + * @package core_grades * @category external * @copyright 2012 Andrew Davis * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.6 + * @since Moodle 2.7 */ defined('MOODLE_INTERNAL') || die(); @@ -29,10 +29,26 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); -require_once($CFG->libdir . '/grade/externallib.php'); +require_once($CFG->dirroot . '/grade/externallib.php'); -class core_grade_external_testcase extends externallib_advanced_testcase { +/** + * Grades functions unit tests + * + * @package core_grades + * @category external + * @copyright 2012 Andrew Davis + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_grades_external_testcase extends externallib_advanced_testcase { + /** + * Load initial test information + * + * @param string $assignmentname Assignment name + * @param int $student1rawgrade Student 1 grade + * @param int $student2rawgrade Student 2 grade + * @return array Array of vars with test information + */ protected function load_test_data($assignmentname, $student1rawgrade, $student2rawgrade) { global $DB; @@ -40,15 +56,15 @@ class core_grade_external_testcase extends externallib_advanced_testcase { $course = $this->getDataGenerator()->create_course(); $coursecontext = context_course::instance($course->id); - $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); - $student1 = $this->getDataGenerator()->create_user(); + $student1 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id); - $student2 = $this->getDataGenerator()->create_user(); + $student2 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id); - $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher')); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); $teacher = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); @@ -70,38 +86,38 @@ class core_grade_external_testcase extends externallib_advanced_testcase { assign_grade_item_update($assignment, $studentgrades); // Insert a custom grade scale to be used by an outcome. - $grade_scale = new grade_scale(); - $grade_scale->name = 'unittestscale3'; - $grade_scale->courseid = $course->id; - $grade_scale->userid = 0; - $grade_scale->scale = 'Distinction, Very Good, Good, Pass, Fail'; - $grade_scale->description = 'This scale is used to mark standard assignments.'; - $grade_scale->insert(); + $gradescale = new grade_scale(); + $gradescale->name = 'unittestscale3'; + $gradescale->courseid = $course->id; + $gradescale->userid = 0; + $gradescale->scale = 'Distinction, Very Good, Good, Pass, Fail'; + $gradescale->description = 'This scale is used to mark standard assignments.'; + $gradescale->insert(); // Insert an outcome. $data = new stdClass(); $data->courseid = $course->id; $data->fullname = 'Team work'; $data->shortname = 'Team work'; - $data->scaleid = $grade_scale->id; + $data->scaleid = $gradescale->id; $outcome = new grade_outcome($data, false); $outcome->insert(); - $outcome_gradeitem = new grade_item(); - $outcome_gradeitem->itemname = $outcome->shortname; - $outcome_gradeitem->itemtype = 'mod'; - $outcome_gradeitem->itemmodule = 'assign'; - $outcome_gradeitem->iteminstance = $assignment->id; - $outcome_gradeitem->outcomeid = $outcome->id; - $outcome_gradeitem->cmid = 0; - $outcome_gradeitem->courseid = $course->id; - $outcome_gradeitem->aggregationcoef = 0; - $outcome_gradeitem->itemnumber = 1; // The activity's original grade item will be 0. - $outcome_gradeitem->gradetype = GRADE_TYPE_SCALE; - $outcome_gradeitem->scaleid = $outcome->scaleid; - $outcome_gradeitem->insert(); + $outcomegradeitem = new grade_item(); + $outcomegradeitem->itemname = $outcome->shortname; + $outcomegradeitem->itemtype = 'mod'; + $outcomegradeitem->itemmodule = 'assign'; + $outcomegradeitem->iteminstance = $assignment->id; + $outcomegradeitem->outcomeid = $outcome->id; + $outcomegradeitem->cmid = 0; + $outcomegradeitem->courseid = $course->id; + $outcomegradeitem->aggregationcoef = 0; + $outcomegradeitem->itemnumber = 1; // The activity's original grade item will be 0. + $outcomegradeitem->gradetype = GRADE_TYPE_SCALE; + $outcomegradeitem->scaleid = $outcome->scaleid; + $outcomegradeitem->insert(); - $assignment_gradeitem = grade_item::fetch( + $assignmentgradeitem = grade_item::fetch( array( 'itemtype' => 'mod', 'itemmodule' => 'assign', @@ -110,8 +126,8 @@ class core_grade_external_testcase extends externallib_advanced_testcase { 'courseid' => $course->id ) ); - $outcome_gradeitem->set_parent($assignment_gradeitem->categoryid); - $outcome_gradeitem->move_after_sortorder($assignment_gradeitem->sortorder); + $outcomegradeitem->set_parent($assignmentgradeitem->categoryid); + $outcomegradeitem->move_after_sortorder($assignmentgradeitem->sortorder); return array($course, $assignment, $student1, $student2, $teacher, $parent); } @@ -128,42 +144,43 @@ class core_grade_external_testcase extends externallib_advanced_testcase { $assignmentname = 'The assignment'; $student1rawgrade = 10; $student2rawgrade = 20; - list($course, $assignment, $student1, $student2, $teacher, $parent) = $this->load_test_data($assignmentname, $student1rawgrade, $student2rawgrade); - $assigment_cm = get_coursemodule_from_id('assign', $assignment->id, 0, false, MUST_EXIST); + list($course, $assignment, $student1, $student2, $teacher, $parent) = + $this->load_test_data($assignmentname, $student1rawgrade, $student2rawgrade); + $assigmentcm = get_coursemodule_from_id('assign', $assignment->id, 0, false, MUST_EXIST); // Student requesting their own grade for the assignment. $this->setUser($student1); - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array($student1->id) ); - $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); - $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + $grades = external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); // Student requesting all of their grades in a course. - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, null, null, array($student1->id) ); - $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $grades = external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); $this->assertTrue(count($grades['items']) == 2); - $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, 'course', $student1->id)); - $outcome = $this->get_outcome($grades, $assigment_cm->id); + $outcome = $this->get_outcome($grades, $assigmentcm->id); $this->assertEquals($outcome['name'], 'Team work'); - $this->assertEquals(0, $this->get_outcome_student_grade($grades, $assigment_cm->id, $student1->id)); + $this->assertEquals(0, $this->get_outcome_student_grade($grades, $assigmentcm->id, $student1->id)); // Student requesting another student's grade for the assignment (should fail). try { - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array($student2->id) ); $this->fail('moodle_exception expected'); @@ -173,21 +190,21 @@ class core_grade_external_testcase extends externallib_advanced_testcase { // Parent requesting their child's grade for the assignment. $this->setUser($parent); - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array($student1->id) ); - $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); - $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + $grades = external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); // Parent requesting another student's grade for the assignment(should fail). try { - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array($student2->id) ); $this->fail('moodle_exception expected'); @@ -197,10 +214,10 @@ class core_grade_external_testcase extends externallib_advanced_testcase { // Student requesting all other student grades for the assignment (should fail). try { - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array($student1->id, $student2->id) ); $this->fail('moodle_exception expected'); @@ -210,10 +227,10 @@ class core_grade_external_testcase extends externallib_advanced_testcase { // Student requesting only grade item information (should fail). try { - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array() ); $this->fail('moodle_exception expected'); @@ -223,47 +240,47 @@ class core_grade_external_testcase extends externallib_advanced_testcase { // Teacher requesting student grades for a course. $this->setUser($teacher); - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array($student1->id, $student2->id) ); - $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); - $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); - $this->assertEquals($student2rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student2->id)); + $grades = external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); + $this->assertEquals($student2rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student2->id)); // Teacher requesting grade item information. - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id + $assigmentcm->id ); - $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); - $activity = $this->get_activity($grades, $assigment_cm->id); + $grades = external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); + $activity = $this->get_activity($grades, $assigmentcm->id); $this->assertEquals($activity['name'], $assignmentname); $this->assertEquals(count($activity['grades']), 0); // Teacher requesting all grade items in a course. - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id ); - $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); + $grades = external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); $this->assertTrue(count($grades['items']) == 2); - $activity = $this->get_activity($grades, $assigment_cm->id); + $activity = $this->get_activity($grades, $assigmentcm->id); $this->assertEquals($activity['name'], $assignmentname); $this->assertEquals(count($activity['grades']), 0); - $outcome = $this->get_outcome($grades, $assigment_cm->id); + $outcome = $this->get_outcome($grades, $assigmentcm->id); $this->assertEquals($outcome['name'], 'Team work'); - // Hide a grade item then have student request it - $result = core_grade_external::update_grades( + // Hide a grade item then have student request it. + $result = core_grades_external::update_grades( 'test', $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, 0, array(), array('hidden' => 1) @@ -276,27 +293,34 @@ class core_grade_external_testcase extends externallib_advanced_testcase { // Student should now not be able to see it. $this->setUser($student1); - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array($student1->id) ); - $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); - $this->assertEquals(null, $this->get_activity($grades, $assigment_cm->id)); + $grades = external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); + $this->assertEquals(null, $this->get_activity($grades, $assigmentcm->id)); // Teacher should still be able to see the hidden grades. $this->setUser($teacher); - $grades = core_grade_external::get_grades( + $grades = core_grades_external::get_grades( $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, array($student1->id) ); - $grades = external_api::clean_returnvalue(core_grade_external::get_grades_returns(), $grades); - $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigment_cm->id, $student1->id)); + $grades = external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); + $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); } + /** + * Get an activity + * + * @param array $grades Array of grades + * @param int $cmid Activity course module id + * @return strdClass Activity object + */ private function get_activity($grades, $cmid) { foreach ($grades['items'] as $item) { if ($item['activityid'] == $cmid) { @@ -306,6 +330,14 @@ class core_grade_external_testcase extends externallib_advanced_testcase { return null; } + /** + * Get a grade for an activity + * + * @param array $grades Array of grades + * @param int $cmid Activity course module id + * @param int $studentid Student it + * @return stdClass Activity Object + */ private function get_activity_student_grade($grades, $cmid, $studentid) { $item = $this->get_activity($grades, $cmid); foreach ($item['grades'] as $grade) { @@ -315,19 +347,34 @@ class core_grade_external_testcase extends externallib_advanced_testcase { } return null; } - + + /** + * Get an ouctome + * + * @param array $grades Array of grades + * @param int $cmid Activity course module id + * @return stdClass Outcome object + */ private function get_outcome($grades, $cmid) { - foreach($grades['outcomes'] as $outcome) { + foreach ($grades['outcomes'] as $outcome) { if ($outcome['activityid'] == $cmid) { return $outcome; } } return null; } - + + /** + * Get a grade from an outcome + * + * @param array $grades Array of grades + * @param int $cmid Activity course module id + * @param int $studentid Student id + * @return stdClass Outcome object + */ private function get_outcome_student_grade($grades, $cmid, $studentid) { $outcome = $this->get_outcome($grades, $cmid); - foreach ($outcome['grades'] as $grade) { + foreach ($outcome['grades'] as $grade) { if ($grade['userid'] == $studentid) { return $grade['grade']; } @@ -336,7 +383,7 @@ class core_grade_external_testcase extends externallib_advanced_testcase { } /** - * Test get_grades() + * Test update_grades() */ public function test_update_grades() { global $DB; @@ -346,18 +393,19 @@ class core_grade_external_testcase extends externallib_advanced_testcase { $assignmentname = 'The assignment'; $student1rawgrade = 10; $student2rawgrade = 20; - list($course, $assignment, $student1, $student2, $teacher, $parent) = $this->load_test_data($assignmentname, $student1rawgrade, $student2rawgrade); - $assigment_cm = get_coursemodule_from_id('assign', $assignment->id, 0, false, MUST_EXIST); + list($course, $assignment, $student1, $student2, $teacher, $parent) = + $this->load_test_data($assignmentname, $student1rawgrade, $student2rawgrade); + $assigmentcm = get_coursemodule_from_id('assign', $assignment->id, 0, false, MUST_EXIST); $this->setUser($teacher); - // Teacher updating grade item information + // Teacher updating grade item information. $changedmax = 93; - $result = core_grade_external::update_grades( + $result = core_grades_external::update_grades( 'test', $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, 0, array(), array('grademax' => $changedmax) @@ -366,13 +414,13 @@ class core_grade_external_testcase extends externallib_advanced_testcase { $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); $this->assertTrue($grades->items[0]->grademax == $changedmax); - // Teacher updating 1 student grade + // Teacher updating 1 student grade. $student1grade = 23; - $result = core_grade_external::update_grades( + $result = core_grades_external::update_grades( 'test', $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, 0, array(array('studentid' => $student1->id, 'grade' => $student1grade)) ); @@ -380,14 +428,14 @@ class core_grade_external_testcase extends externallib_advanced_testcase { $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id, array($student1->id)); $this->assertTrue($grades->items[0]->grades[$student1->id]->grade == $student1grade); - // Teacher updating multiple student grades + // Teacher updating multiple student grades. $student1grade = 11; $student2grade = 13; - $result = core_grade_external::update_grades( + $result = core_grades_external::update_grades( 'test', $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, 0, array( array('studentid' => $student1->id, 'grade' => $student1grade), @@ -399,15 +447,15 @@ class core_grade_external_testcase extends externallib_advanced_testcase { $this->assertTrue($grades->items[0]->grades[$student1->id]->grade == $student1grade); $this->assertTrue($grades->items[0]->grades[$student2->id]->grade == $student2grade); - // Student attempting to update their own grade (should fail) + // Student attempting to update their own grade (should fail). $this->setUser($student1); try { $student1grade = 17; - $result = core_grade_external::update_grades( + $result = core_grades_external::update_grades( 'test', $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, 0, array( array('studentid' => $student1->id, 'grade' => $student1grade)) ); @@ -416,15 +464,15 @@ class core_grade_external_testcase extends externallib_advanced_testcase { $this->assertTrue(true); } - // Parent attempting to update their child's grade (should fail) + // Parent attempting to update their child's grade (should fail). $this->setUser($parent); try { $student1grade = 13; - $result = core_grade_external::update_grades( + $result = core_grades_external::update_grades( 'test', $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, 0, array( array('studentid' => $student1->id, 'grade' => $student1grade)) ); @@ -436,11 +484,11 @@ class core_grade_external_testcase extends externallib_advanced_testcase { // Student trying to hide a grade item (should fail). $this->setUser($student1); try { - $result = core_grade_external::update_grades( + $result = core_grades_external::update_grades( 'test', $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, 0, array(), array('hidden' => 1) @@ -451,7 +499,7 @@ class core_grade_external_testcase extends externallib_advanced_testcase { } // Give the student role 'moodle/grade:hide' and they should now be able to hide the grade item. - $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); $coursecontext = context_course::instance($course->id); assign_capability('moodle/grade:hide', CAP_ALLOW, $studentrole->id, $coursecontext->id); accesslib_clear_all_caches_for_unit_testing(); @@ -460,11 +508,11 @@ class core_grade_external_testcase extends externallib_advanced_testcase { $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); $this->assertTrue($grades->items[0]->hidden == 0); - $result = core_grade_external::update_grades( + $result = core_grades_external::update_grades( 'test', $course->id, 'mod_assign', - $assigment_cm->id, + $assigmentcm->id, 0, array(), array('hidden' => 1) diff --git a/grade/upgrade.txt b/grade/upgrade.txt deleted file mode 100644 index a313a200a54..00000000000 --- a/grade/upgrade.txt +++ /dev/null @@ -1,8 +0,0 @@ -This files describes API changes in /grade/*, -information provided here is intended especially for developers. - - -=== 2.7.0 === -* The class core_grade_external in grade/externallib.php has been renamed to core_grading_external. - grade/externallib.php is concerned with the grading API not the grades API. - The change was necessary to avoid a duplicate class name with core_grade_external in lib/grade/externallib.php. diff --git a/lib/db/services.php b/lib/db/services.php index 1078f1e04a8..e5a9642bf11 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -99,24 +99,24 @@ $functions = array( 'type' => 'read', 'capabilities'=> 'moodle/cohort:view', ), - // === grade related functions === + // Grade related functions. - 'core_grade_get_grades' => array( - 'classname' => 'core_grade_external', - 'methodname' => 'get_grades', - 'classpath' => 'lib/grade/externallib.php', - 'description' => 'Returns grade item details and optionally student grades.', - 'type' => 'read', - 'capabilities'=> 'moodle/grade:view, moodle/grade:viewall', + 'core_grades_get_grades' => array( + 'classname' => 'core_grades_external', + 'methodname' => 'get_grades', + 'classpath' => 'grade/externallib.php', + 'description' => 'Returns grade item details and optionally student grades.', + 'type' => 'read', + 'capabilities' => 'moodle/grade:view, moodle/grade:viewall', ), - 'core_grade_update_grades' => array( - 'classname' => 'core_grade_external', - 'methodname' => 'update_grades', - 'classpath' => 'lib/grade/externallib.php', - 'description' => 'Update a grade item and associated student grades.', - 'type' => 'write', - 'capabilities'=> '', + 'core_grades_update_grades' => array( + 'classname' => 'core_grades_external', + 'methodname' => 'update_grades', + 'classpath' => 'grade/externallib.php', + 'description' => 'Update a grade item and associated student grades.', + 'type' => 'write', + 'capabilities' => '', ), // === group related functions === diff --git a/lib/grade/externallib.php b/lib/grade/externallib.php deleted file mode 100644 index 7471ed2a124..00000000000 --- a/lib/grade/externallib.php +++ /dev/null @@ -1,448 +0,0 @@ -. - - -/** - * External grade API - * - * @package core_grade - * @category external - * @copyright 2012 Andrew Davis - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once("$CFG->libdir/externallib.php"); - -/** - * Grade external functions - * - * @package core_grade - * @category external - * @copyright 2012 Andrew Davis - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.6 - */ -class core_grade_external extends external_api { - /** - * Returns description of method parameters - * - * @return external_function_parameters - * @since Moodle 2.6 - */ - public static function get_grades_parameters() { - return new external_function_parameters( - array( - 'courseid' => new external_value(PARAM_INT, 'id of course'), - 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''), - 'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null), - 'userids' => new external_multiple_structure( - new external_value(PARAM_INT, 'user ID'), 'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array() - ) - ) - ); - } - - /** - * Retrieve grade items and, optionally, student grades - * - * @param array $grades array of grade information - * @return array of newly created groups - * @since Moodle 2.6 - */ - public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) { - global $CFG, $USER, $DB; - require_once("$CFG->libdir/gradelib.php"); - - $params = self::validate_parameters(self::get_grades_parameters(), - array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids)); - - $coursecontext = context_course::instance($params['courseid']); - - try { - self::validate_context($coursecontext); - } catch (Exception $e) { - $exceptionparam = new stdClass(); - $exceptionparam->message = $e->getMessage(); - $exceptionparam->courseid = $params['courseid']; - throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); - } - - $course = $DB->get_record('course', array('id' => $params['courseid'])); - - $access = false; - if (has_capability('moodle/grade:viewall', $coursecontext)) { - // Can view all user's grades in this course. - $access = true; - - } else if ($course->showgrades && count($params['userids']) == 1) { - // Course showgrades == students/parents can access grades. - - if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) { - // Student can view their own grades in this course. - $access = true; - - } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) { - // User can view the grades of this user. Parent most probably. - $access = true; - } - } - - if (!$access) { - throw new moodle_exception('nopermissiontoviewgrades', 'error'); - } - - $itemtype = null; - $itemmodule = null; - if (!empty($params['component'])) { - list($itemtype, $itemmodule) = normalize_component($params['component']); - } - - $cm = null; - if (!empty($itemmodule) && !empty($activityid)) { - if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { - throw new moodle_exception('invalidcoursemodule'); - } - } - - $cminstanceid = null; - if (!empty($cm)) { - $cminstanceid = $cm->instance; - } - $grades = grade_get_grades($params['courseid'], $itemtype, $itemmodule, $cminstanceid, $params['userids']); - - $activity_instances = null; - if (empty($cm)) { - // If we're dealing with multiple activites load all the module info. - $modinfo = get_fast_modinfo($params['courseid']); - $activity_instances = $modinfo->get_instances(); - } - - foreach ($grades->items as $gradeitem) { - if (!empty($cm)) { - // If they only requested one activity we will already have the cm. - $modulecm = $cm; - } else if (!empty($gradeitem->itemmodule)) { - $modulecm = $activity_instances[$gradeitem->itemmodule][$gradeitem->iteminstance]; - } else { - // Course grade item. - continue; - } - - // Make student feedback ready for output. - foreach ($gradeitem->grades as $studentgrade) { - if (!empty($studentgrade->feedback)) { - list($studentgrade->feedback, $categoryinfo->feedbackformat) = - external_format_text($studentgrade->feedback, $studentgrade->feedbackformat, - $modulecm->id, $params['component'], 'feedback', null); - } - } - } - - // Convert from objects to arrays so all web service clients are supported. - // While we're doing that we also remove grades the current user can't see due to hiding - $grades_array = array(); - $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid'])); - - $grades_array['items'] = array(); - foreach ($grades->items as $grade_item) { - // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID. - $grade_item_instance = self::get_grade_item($course->id, $grade_item->itemtype, $grade_item->itemmodule, $grade_item->iteminstance, 0); - if (!$canviewhidden && $grade_item_instance->is_hidden()) { - continue; - } - $grade_item_array = (array)$grade_item; - $grade_item_array['grades'] = array(); - - if (!empty($grade_item->grades)) { - foreach ($grade_item->grades as $studentid => $studentgrade) { - $grade_grade_instance = grade_grade::fetch( - array( - 'userid' => $studentid, - 'itemid' => $grade_item_instance->id - ) - ); - if (!$canviewhidden && $grade_grade_instance->is_hidden()) { - continue; - } - $grade_item_array['grades'][$studentid] = (array)$studentgrade; - // Add the student ID as some WS clients can't access the array key. - $grade_item_array['grades'][$studentid]['userid'] = $studentid; - } - } - - // If they requested grades for multiple activities load the cm object now. - $modulecm = $cm; - if (empty($modulecm) && !empty($grade_item_instance->itemmodule)) { - $modulecm = $activity_instances[$grade_item_instance->itemmodule][$grade_item_instance->iteminstance]; - } - if ($grade_item_instance->itemtype == 'course') { - $grades_array['items']['course'] = $grade_item_array; - $grades_array['items']['course']['activityid'] = 'course'; - } else { - $grades_array['items'][$modulecm->id] = $grade_item_array; - // Add the activity ID as some WS clients can't access the array key. - $grades_array['items'][$modulecm->id]['activityid'] = $modulecm->id; - } - } - - $grades_array['outcomes'] = array(); - foreach ($grades->outcomes as $outcome) { - $modulecm = $cm; - if (empty($modulecm)) { - $modulecm = $activity_instances[$outcome->itemmodule][$outcome->iteminstance]; - } - $grades_array['outcomes'][$modulecm->id] = (array)$outcome; - $grades_array['outcomes'][$modulecm->id]['activityid'] = $modulecm->id; - - $grades_array['outcomes'][$modulecm->id]['grades'] = array(); - if (!empty($outcome->grades)) { - foreach ($outcome->grades as $studentid => $studentgrade) { - if (!$canviewhidden) { - // Need to load the grade_grade object to check visibility. - $grade_item_instance = self::get_grade_item($course->id, $outcome->itemtype, $outcome->itemmodule, $outcome->iteminstance, $outcome->itemnumber); - $grade_grade_instance = grade_grade::fetch( - array( - 'userid' => $studentid, - 'itemid' => $grade_item_instance->id - ) - ); - // The grade grade may be legitimately missing if the student has no grade. - if (!empty($grade_grade_instance) && $grade_grade_instance->is_hidden()) { - continue; - } - } - $grades_array['outcomes'][$modulecm->id]['grades'][$studentid] = (array)$studentgrade; - - // Add the student ID into the grade structure as some WS clients can't access the key. - $grades_array['outcomes'][$modulecm->id]['grades'][$studentid]['userid'] = $studentid; - } - } - } - - return $grades_array; - } - - private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) { - $grade_item_instance = null; - if ($itemtype == 'course') { - $grade_item_instance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype)); - } else { - $grade_item_instance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype, 'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber)); - } - return $grade_item_instance; - } - - /** - * Returns description of method result value - * - * @return external_description - * @since Moodle 2.6 - */ - public static function get_grades_returns() { - return new external_single_structure( - array( - 'items' => new external_multiple_structure( - new external_single_structure( - array( - 'activityid' => new external_value(PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), - 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), - 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), - 'name' => new external_value(PARAM_RAW, 'The module name'), - 'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'), - 'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'), - 'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'), - 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), - 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), - 'grades' => new external_multiple_structure( - new external_single_structure( - array( - 'userid' => new external_value(PARAM_INT, 'Student ID'), - 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), - 'locked' => new external_value(PARAM_BOOL, 'Is the student\'s grade locked?'), - 'hidden' => new external_value(PARAM_BOOL, 'Is the student\'s grade hidden?'), - 'overridden' => new external_value(PARAM_BOOL, 'Is the student\'s grade overridden?'), - 'feedback' => new external_value(PARAM_RAW, 'Feedback from the grader'), - 'feedbackformat' => new external_value(PARAM_INT, 'The format of the feedback'), - 'usermodified' => new external_value(PARAM_INT, 'The ID of the last user to modify this student grade'), - 'datesubmitted' => new external_value(PARAM_INT, 'A timestamp indicating when the student submitted the activity'), - 'dategraded' => new external_value(PARAM_INT, 'A timestamp indicating when the assignment was grades'), - 'str_grade' => new external_value(PARAM_RAW, 'A string representation of the grade'), - 'str_long_grade' => new external_value(PARAM_RAW, 'A nicely formatted string representation of the grade'), - 'str_feedback' => new external_value(PARAM_TEXT, 'A string representation of the feedback from the grader'), - ) - ) - ), - ) - ) - ), - 'outcomes' => new external_multiple_structure( - new external_single_structure( - array( - 'activityid' => new external_value(PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), - 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), - 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), - 'name' => new external_value(PARAM_RAW, 'The module name'), - 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), - 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), - 'grades' => new external_multiple_structure( - new external_single_structure( - array( - 'userid' => new external_value(PARAM_INT, 'Student ID'), - 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), - 'locked' => new external_value(PARAM_BOOL, 'Is the student\'s grade locked?'), - 'hidden' => new external_value(PARAM_BOOL, 'Is the student\'s grade hidden?'), - 'feedback' => new external_value(PARAM_RAW, 'Feedback from the grader'), - 'feedbackformat' => new external_value(PARAM_INT, 'The feedback format'), - 'usermodified' => new external_value(PARAM_INT, 'The ID of the last user to modify this student grade'), - 'str_grade' => new external_value(PARAM_RAW, 'A string representation of the grade'), - 'str_feedback' => new external_value(PARAM_TEXT, 'A string representation of the feedback from the grader'), - ) - ) - ), - ) - ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL - ) - ) - ); - - } - - /** - * Returns description of method parameters - * - * @return external_function_parameters - * @since Moodle 2.6 - */ - public static function update_grades_parameters() { - return new external_function_parameters( - array( - 'source' => new external_value(PARAM_TEXT, 'The source of the grade update'), - 'courseid' => new external_value(PARAM_INT, 'id of course'), - 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'), - 'activityid' => new external_value(PARAM_INT, 'The activity ID'), - 'itemnumber' => new external_value(PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'), - 'grades' => new external_multiple_structure( - new external_single_structure( - array( - 'studentid' => new external_value(PARAM_INT, 'Student ID'), - 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), - 'str_feedback' => new external_value(PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL), - ) - ), 'Any student grades to alter', VALUE_OPTIONAL), - 'itemdetails' => new external_single_structure( - array( - 'itemname' => new external_value(PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL), - 'idnumber' => new external_value(PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL), - 'gradetype' => new external_value(PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL), - 'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL), - 'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL), - 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL), - 'multfactor' => new external_value(PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL), - 'plusfactor' => new external_value(PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL), - 'deleted' => new external_value(PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL), - 'hidden' => new external_value(PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL), - ), 'Any grade item settings to alter', VALUE_OPTIONAL - ) - ) - ); - } - - /** - * Update a grade item and, optionally, student grades - * - * @param array $grade array of grade information - * @since Moodle 2.6 - */ - public static function update_grades($source, $courseid, $component, $activityid, $itemnumber, $grades = array(), $itemdetails = array()) { - global $CFG; - - require_once("$CFG->libdir/gradelib.php"); - - $params = self::validate_parameters( - self::update_grades_parameters(), - array( - 'source' => $source, - 'courseid' => $courseid, - 'component' => $component, - 'activityid' => $activityid, - 'itemnumber' => $itemnumber, - 'grades' => $grades, - 'itemdetails' => $itemdetails - ) - ); - - list($itemtype, $itemmodule) = normalize_component($params['component']); - - if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { - throw new moodle_exception('invalidcoursemodule'); - } - $iteminstance = $cm->instance; - - $coursecontext = context_course::instance($params['courseid']); - - try { - self::validate_context($coursecontext); - } catch (Exception $e) { - $exceptionparam = new stdClass(); - $exceptionparam->message = $e->getMessage(); - $exceptionparam->courseid = $params['courseid']; - throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); - } - - $hidinggrades = false; - $editinggradeitem = false; - $editinggrades = false; - - $gradestructure = array(); - foreach ($grades as $grade) { - $editinggrades = true; - $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']); - } - if (!empty($params['itemdetails'])) { - if (isset($params['itemdetails']['hidden'])) { - $hidinggrades = true; - } else { - $editinggradeitem = true; - } - } - - if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) { - throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, 'moodle/grade:manage required to edit grade information'); - } - if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) && !has_capability('moodle/grade:hide', $coursecontext)) { - throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, 'moodle/grade:hide required to hide grade items'); - } - if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) { - throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, 'moodle/grade:edit required to edit grades'); - } - - return grade_update($params['source'], $params['courseid'], $itemtype, $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']); - } - - /** - * Returns description of method result value - * - * @return external_description - * @since Moodle 2.6 - */ - public static function update_grades_returns() { - return new external_single_structure( - array ( - 'result' => new external_value(PARAM_INT, 'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED as defined in lib/grade/constants.php') - ) - ); - } -} diff --git a/lib/gradelib.php b/lib/gradelib.php index 7cda902f28a..3ad3a6a7daa 100644 --- a/lib/gradelib.php +++ b/lib/gradelib.php @@ -353,7 +353,7 @@ function grade_get_grades($courseid, $itemtype = null, $itemmodule = null, $item } } - $params = array('courseid'=>$courseid); + $params = array('courseid' => $courseid); if (!empty($itemtype)) { $params['itemtype'] = $itemtype; } From 19ed1797164e55c0bf1e71a364d882e5839b9fa4 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 24 Mar 2014 09:39:32 +0100 Subject: [PATCH 3/3] MDL-30085 Moved to lib/classes for support autoloading --- grade/externallib.php | 491 ----------------- lib/classes/grades_external.php | 511 ++++++++++++++++++ lib/db/services.php | 2 - .../tests/grades_externallib_test.php | 3 +- 4 files changed, 512 insertions(+), 495 deletions(-) create mode 100644 lib/classes/grades_external.php rename grade/tests/externallib_grades_test.php => lib/tests/grades_externallib_test.php (99%) diff --git a/grade/externallib.php b/grade/externallib.php index baa6506ab37..ef5e6988f7a 100644 --- a/grade/externallib.php +++ b/grade/externallib.php @@ -49,494 +49,3 @@ class core_grade_external extends external_api { } } - -/** - * Core grades external functions - * - * @package core_grades - * @category external - * @copyright 2012 Andrew Davis - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.7 - */ -class core_grades_external extends external_api { - /** - * Returns description of method parameters - * - * @return external_function_parameters - * @since Moodle 2.7 - */ - public static function get_grades_parameters() { - return new external_function_parameters( - array( - 'courseid' => new external_value(PARAM_INT, 'id of course'), - 'component' => new external_value( - PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''), - 'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null), - 'userids' => new external_multiple_structure( - new external_value(PARAM_INT, 'user ID'), - 'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array() - ) - ) - ); - } - - /** - * Retrieve grade items and, optionally, student grades - * - * @param int $courseid Course id - * @param string $component Component name - * @param int $activityid Activity id - * @param array $userids Array of user ids - * @return array Array of grades - * @since Moodle 2.7 - */ - public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) { - global $CFG, $USER, $DB; - require_once("$CFG->libdir/gradelib.php"); - - $params = self::validate_parameters(self::get_grades_parameters(), - array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids)); - - $coursecontext = context_course::instance($params['courseid']); - - try { - self::validate_context($coursecontext); - } catch (Exception $e) { - $exceptionparam = new stdClass(); - $exceptionparam->message = $e->getMessage(); - $exceptionparam->courseid = $params['courseid']; - throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); - } - - $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST); - - $access = false; - if (has_capability('moodle/grade:viewall', $coursecontext)) { - // Can view all user's grades in this course. - $access = true; - - } else if ($course->showgrades && count($params['userids']) == 1) { - // Course showgrades == students/parents can access grades. - - if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) { - // Student can view their own grades in this course. - $access = true; - - } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) { - // User can view the grades of this user. Parent most probably. - $access = true; - } - } - - if (!$access) { - throw new moodle_exception('nopermissiontoviewgrades', 'error'); - } - - $itemtype = null; - $itemmodule = null; - if (!empty($params['component'])) { - list($itemtype, $itemmodule) = normalize_component($params['component']); - } - - $cm = null; - if (!empty($itemmodule) && !empty($activityid)) { - if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { - throw new moodle_exception('invalidcoursemodule'); - } - } - - $cminstanceid = null; - if (!empty($cm)) { - $cminstanceid = $cm->instance; - } - $grades = grade_get_grades($params['courseid'], $itemtype, $itemmodule, $cminstanceid, $params['userids']); - - $acitivityinstances = null; - if (empty($cm)) { - // If we're dealing with multiple activites load all the module info. - $modinfo = get_fast_modinfo($params['courseid']); - $acitivityinstances = $modinfo->get_instances(); - } - - foreach ($grades->items as $gradeitem) { - if (!empty($cm)) { - // If they only requested one activity we will already have the cm. - $modulecm = $cm; - } else if (!empty($gradeitem->itemmodule)) { - $modulecm = $acitivityinstances[$gradeitem->itemmodule][$gradeitem->iteminstance]; - } else { - // Course grade item. - continue; - } - - // Make student feedback ready for output. - foreach ($gradeitem->grades as $studentgrade) { - if (!empty($studentgrade->feedback)) { - list($studentgrade->feedback, $categoryinfo->feedbackformat) = - external_format_text($studentgrade->feedback, $studentgrade->feedbackformat, - $modulecm->id, $params['component'], 'feedback', null); - } - } - } - - // Convert from objects to arrays so all web service clients are supported. - // While we're doing that we also remove grades the current user can't see due to hiding. - $gradesarray = array(); - $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid'])); - - $gradesarray['items'] = array(); - foreach ($grades->items as $gradeitem) { - // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID. - $gradeiteminstance = self::get_grade_item( - $course->id, $gradeitem->itemtype, $gradeitem->itemmodule, $gradeitem->iteminstance, 0); - if (!$canviewhidden && $gradeiteminstance->is_hidden()) { - continue; - } - $gradeitemarray = (array)$gradeitem; - $gradeitemarray['grades'] = array(); - - if (!empty($gradeitem->grades)) { - foreach ($gradeitem->grades as $studentid => $studentgrade) { - $gradegradeinstance = grade_grade::fetch( - array( - 'userid' => $studentid, - 'itemid' => $gradeiteminstance->id - ) - ); - if (!$canviewhidden && $gradegradeinstance->is_hidden()) { - continue; - } - $gradeitemarray['grades'][$studentid] = (array)$studentgrade; - // Add the student ID as some WS clients can't access the array key. - $gradeitemarray['grades'][$studentid]['userid'] = $studentid; - } - } - - // If they requested grades for multiple activities load the cm object now. - $modulecm = $cm; - if (empty($modulecm) && !empty($gradeiteminstance->itemmodule)) { - $modulecm = $acitivityinstances[$gradeiteminstance->itemmodule][$gradeiteminstance->iteminstance]; - } - if ($gradeiteminstance->itemtype == 'course') { - $gradesarray['items']['course'] = $gradeitemarray; - $gradesarray['items']['course']['activityid'] = 'course'; - } else { - $gradesarray['items'][$modulecm->id] = $gradeitemarray; - // Add the activity ID as some WS clients can't access the array key. - $gradesarray['items'][$modulecm->id]['activityid'] = $modulecm->id; - } - } - - $gradesarray['outcomes'] = array(); - foreach ($grades->outcomes as $outcome) { - $modulecm = $cm; - if (empty($modulecm)) { - $modulecm = $acitivityinstances[$outcome->itemmodule][$outcome->iteminstance]; - } - $gradesarray['outcomes'][$modulecm->id] = (array)$outcome; - $gradesarray['outcomes'][$modulecm->id]['activityid'] = $modulecm->id; - - $gradesarray['outcomes'][$modulecm->id]['grades'] = array(); - if (!empty($outcome->grades)) { - foreach ($outcome->grades as $studentid => $studentgrade) { - if (!$canviewhidden) { - // Need to load the grade_grade object to check visibility. - $gradeiteminstance = self::get_grade_item( - $course->id, $outcome->itemtype, $outcome->itemmodule, $outcome->iteminstance, $outcome->itemnumber); - $gradegradeinstance = grade_grade::fetch( - array( - 'userid' => $studentid, - 'itemid' => $gradeiteminstance->id - ) - ); - // The grade grade may be legitimately missing if the student has no grade. - if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) { - continue; - } - } - $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid] = (array)$studentgrade; - - // Add the student ID into the grade structure as some WS clients can't access the key. - $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid]['userid'] = $studentid; - } - } - } - - return $gradesarray; - } - - /** - * Get a grade item - * @param int $courseid Course id - * @param string $itemtype Item type - * @param string $itemmodule Item module - * @param int $iteminstance Item instance - * @param int $itemnumber Item number - * @return grade_item A grade_item instance - */ - private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) { - $gradeiteminstance = null; - if ($itemtype == 'course') { - $gradeiteminstance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype)); - } else { - $gradeiteminstance = grade_item::fetch( - array('courseid' => $courseid, 'itemtype' => $itemtype, - 'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber)); - } - return $gradeiteminstance; - } - - /** - * Returns description of method result value - * - * @return external_description - * @since Moodle 2.7 - */ - public static function get_grades_returns() { - return new external_single_structure( - array( - 'items' => new external_multiple_structure( - new external_single_structure( - array( - 'activityid' => new external_value( - PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), - 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), - 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), - 'name' => new external_value(PARAM_RAW, 'The module name'), - 'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'), - 'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'), - 'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'), - 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), - 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), - 'grades' => new external_multiple_structure( - new external_single_structure( - array( - 'userid' => new external_value( - PARAM_INT, 'Student ID'), - 'grade' => new external_value( - PARAM_FLOAT, 'Student grade'), - 'locked' => new external_value( - PARAM_BOOL, 'Is the student\'s grade locked?'), - 'hidden' => new external_value( - PARAM_BOOL, 'Is the student\'s grade hidden?'), - 'overridden' => new external_value( - PARAM_BOOL, 'Is the student\'s grade overridden?'), - 'feedback' => new external_value( - PARAM_RAW, 'Feedback from the grader'), - 'feedbackformat' => new external_value( - PARAM_INT, 'The format of the feedback'), - 'usermodified' => new external_value( - PARAM_INT, 'The ID of the last user to modify this student grade'), - 'datesubmitted' => new external_value( - PARAM_INT, 'A timestamp indicating when the student submitted the activity'), - 'dategraded' => new external_value( - PARAM_INT, 'A timestamp indicating when the assignment was grades'), - 'str_grade' => new external_value( - PARAM_RAW, 'A string representation of the grade'), - 'str_long_grade' => new external_value( - PARAM_RAW, 'A nicely formatted string representation of the grade'), - 'str_feedback' => new external_value( - PARAM_TEXT, 'A string representation of the feedback from the grader'), - ) - ) - ), - ) - ) - ), - 'outcomes' => new external_multiple_structure( - new external_single_structure( - array( - 'activityid' => new external_value( - PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), - 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), - 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), - 'name' => new external_value(PARAM_RAW, 'The module name'), - 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), - 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), - 'grades' => new external_multiple_structure( - new external_single_structure( - array( - 'userid' => new external_value( - PARAM_INT, 'Student ID'), - 'grade' => new external_value( - PARAM_FLOAT, 'Student grade'), - 'locked' => new external_value( - PARAM_BOOL, 'Is the student\'s grade locked?'), - 'hidden' => new external_value( - PARAM_BOOL, 'Is the student\'s grade hidden?'), - 'feedback' => new external_value( - PARAM_RAW, 'Feedback from the grader'), - 'feedbackformat' => new external_value( - PARAM_INT, 'The feedback format'), - 'usermodified' => new external_value( - PARAM_INT, 'The ID of the last user to modify this student grade'), - 'str_grade' => new external_value( - PARAM_RAW, 'A string representation of the grade'), - 'str_feedback' => new external_value( - PARAM_TEXT, 'A string representation of the feedback from the grader'), - ) - ) - ), - ) - ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL - ) - ) - ); - - } - - /** - * Returns description of method parameters - * - * @return external_function_parameters - * @since Moodle 2.7 - */ - public static function update_grades_parameters() { - return new external_function_parameters( - array( - 'source' => new external_value(PARAM_TEXT, 'The source of the grade update'), - 'courseid' => new external_value(PARAM_INT, 'id of course'), - 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'), - 'activityid' => new external_value(PARAM_INT, 'The activity ID'), - 'itemnumber' => new external_value( - PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'), - 'grades' => new external_multiple_structure( - new external_single_structure( - array( - 'studentid' => new external_value(PARAM_INT, 'Student ID'), - 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), - 'str_feedback' => new external_value( - PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL), - ) - ), 'Any student grades to alter', VALUE_OPTIONAL), - 'itemdetails' => new external_single_structure( - array( - 'itemname' => new external_value( - PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL), - 'idnumber' => new external_value( - PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL), - 'gradetype' => new external_value( - PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL), - 'grademax' => new external_value( - PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL), - 'grademin' => new external_value( - PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL), - 'scaleid' => new external_value( - PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL), - 'multfactor' => new external_value( - PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL), - 'plusfactor' => new external_value( - PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL), - 'deleted' => new external_value( - PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL), - 'hidden' => new external_value( - PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL), - ), 'Any grade item settings to alter', VALUE_OPTIONAL - ) - ) - ); - } - - /** - * Update a grade item and, optionally, student grades - * - * @param string $source The source of the grade update - * @param int $courseid The course id - * @param string $component Component name - * @param int $activityid The activity id - * @param int $itemnumber The item number - * @param array $grades Array of grades - * @param array $itemdetails Array of item details - * @return int A status flag - * @since Moodle 2.7 - */ - public static function update_grades($source, $courseid, $component, $activityid, - $itemnumber, $grades = array(), $itemdetails = array()) { - global $CFG; - - require_once("$CFG->libdir/gradelib.php"); - - $params = self::validate_parameters( - self::update_grades_parameters(), - array( - 'source' => $source, - 'courseid' => $courseid, - 'component' => $component, - 'activityid' => $activityid, - 'itemnumber' => $itemnumber, - 'grades' => $grades, - 'itemdetails' => $itemdetails - ) - ); - - list($itemtype, $itemmodule) = normalize_component($params['component']); - - if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { - throw new moodle_exception('invalidcoursemodule'); - } - $iteminstance = $cm->instance; - - $coursecontext = context_course::instance($params['courseid']); - - try { - self::validate_context($coursecontext); - } catch (Exception $e) { - $exceptionparam = new stdClass(); - $exceptionparam->message = $e->getMessage(); - $exceptionparam->courseid = $params['courseid']; - throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); - } - - $hidinggrades = false; - $editinggradeitem = false; - $editinggrades = false; - - $gradestructure = array(); - foreach ($grades as $grade) { - $editinggrades = true; - $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']); - } - if (!empty($params['itemdetails'])) { - if (isset($params['itemdetails']['hidden'])) { - $hidinggrades = true; - } else { - $editinggradeitem = true; - } - } - - if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) { - throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, - 'moodle/grade:manage required to edit grade information'); - } - if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) && - !has_capability('moodle/grade:hide', $coursecontext)) { - throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, - 'moodle/grade:hide required to hide grade items'); - } - if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) { - throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, - 'moodle/grade:edit required to edit grades'); - } - - return grade_update($params['source'], $params['courseid'], $itemtype, - $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']); - } - - /** - * Returns description of method result value - * - * @return external_description - * @since Moodle 2.7 - */ - public static function update_grades_returns() { - return new external_single_structure( - array ( - 'result' => new external_value( - PARAM_INT, - 'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED - as defined in lib/grade/constants.php') - ) - ); - } -} diff --git a/lib/classes/grades_external.php b/lib/classes/grades_external.php new file mode 100644 index 00000000000..5fcee4094cd --- /dev/null +++ b/lib/classes/grades_external.php @@ -0,0 +1,511 @@ +. + +/** + * Core grades external functions + * + * @package core_grades + * @copyright 2012 Andrew Davis + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.7 + */ + +defined('MOODLE_INTERNAL') || die; + +require_once("$CFG->libdir/externallib.php"); +require_once("$CFG->libdir/gradelib.php"); + +/** + * core grades functions + */ +class core_grades_external extends external_api { + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 2.7 + */ + public static function get_grades_parameters() { + return new external_function_parameters( + array( + 'courseid' => new external_value(PARAM_INT, 'id of course'), + 'component' => new external_value( + PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz', VALUE_DEFAULT, ''), + 'activityid' => new external_value(PARAM_INT, 'The activity ID', VALUE_DEFAULT, null), + 'userids' => new external_multiple_structure( + new external_value(PARAM_INT, 'user ID'), + 'An array of user IDs, leave empty to just retrieve grade item information', VALUE_DEFAULT, array() + ) + ) + ); + } + + /** + * Retrieve grade items and, optionally, student grades + * + * @param int $courseid Course id + * @param string $component Component name + * @param int $activityid Activity id + * @param array $userids Array of user ids + * @return array Array of grades + * @since Moodle 2.7 + */ + public static function get_grades($courseid, $component = null, $activityid = null, $userids = array()) { + global $CFG, $USER, $DB; + + $params = self::validate_parameters(self::get_grades_parameters(), + array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids)); + + $coursecontext = context_course::instance($params['courseid']); + + try { + self::validate_context($coursecontext); + } catch (Exception $e) { + $exceptionparam = new stdClass(); + $exceptionparam->message = $e->getMessage(); + $exceptionparam->courseid = $params['courseid']; + throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); + } + + $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST); + + $access = false; + if (has_capability('moodle/grade:viewall', $coursecontext)) { + // Can view all user's grades in this course. + $access = true; + + } else if ($course->showgrades && count($params['userids']) == 1) { + // Course showgrades == students/parents can access grades. + + if ($params['userids'][0] == $USER->id and has_capability('moodle/grade:view', $coursecontext)) { + // Student can view their own grades in this course. + $access = true; + + } else if (has_capability('moodle/grade:viewall', context_user::instance($params['userids'][0]))) { + // User can view the grades of this user. Parent most probably. + $access = true; + } + } + + if (!$access) { + throw new moodle_exception('nopermissiontoviewgrades', 'error'); + } + + $itemtype = null; + $itemmodule = null; + if (!empty($params['component'])) { + list($itemtype, $itemmodule) = normalize_component($params['component']); + } + + $cm = null; + if (!empty($itemmodule) && !empty($activityid)) { + if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { + throw new moodle_exception('invalidcoursemodule'); + } + } + + $cminstanceid = null; + if (!empty($cm)) { + $cminstanceid = $cm->instance; + } + $grades = grade_get_grades($params['courseid'], $itemtype, $itemmodule, $cminstanceid, $params['userids']); + + $acitivityinstances = null; + if (empty($cm)) { + // If we're dealing with multiple activites load all the module info. + $modinfo = get_fast_modinfo($params['courseid']); + $acitivityinstances = $modinfo->get_instances(); + } + + foreach ($grades->items as $gradeitem) { + if (!empty($cm)) { + // If they only requested one activity we will already have the cm. + $modulecm = $cm; + } else if (!empty($gradeitem->itemmodule)) { + $modulecm = $acitivityinstances[$gradeitem->itemmodule][$gradeitem->iteminstance]; + } else { + // Course grade item. + continue; + } + + // Make student feedback ready for output. + foreach ($gradeitem->grades as $studentgrade) { + if (!empty($studentgrade->feedback)) { + list($studentgrade->feedback, $categoryinfo->feedbackformat) = + external_format_text($studentgrade->feedback, $studentgrade->feedbackformat, + $modulecm->id, $params['component'], 'feedback', null); + } + } + } + + // Convert from objects to arrays so all web service clients are supported. + // While we're doing that we also remove grades the current user can't see due to hiding. + $gradesarray = array(); + $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid'])); + + $gradesarray['items'] = array(); + foreach ($grades->items as $gradeitem) { + // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID. + $gradeiteminstance = self::get_grade_item( + $course->id, $gradeitem->itemtype, $gradeitem->itemmodule, $gradeitem->iteminstance, 0); + if (!$canviewhidden && $gradeiteminstance->is_hidden()) { + continue; + } + $gradeitemarray = (array)$gradeitem; + $gradeitemarray['grades'] = array(); + + if (!empty($gradeitem->grades)) { + foreach ($gradeitem->grades as $studentid => $studentgrade) { + $gradegradeinstance = grade_grade::fetch( + array( + 'userid' => $studentid, + 'itemid' => $gradeiteminstance->id + ) + ); + if (!$canviewhidden && $gradegradeinstance->is_hidden()) { + continue; + } + $gradeitemarray['grades'][$studentid] = (array)$studentgrade; + // Add the student ID as some WS clients can't access the array key. + $gradeitemarray['grades'][$studentid]['userid'] = $studentid; + } + } + + // If they requested grades for multiple activities load the cm object now. + $modulecm = $cm; + if (empty($modulecm) && !empty($gradeiteminstance->itemmodule)) { + $modulecm = $acitivityinstances[$gradeiteminstance->itemmodule][$gradeiteminstance->iteminstance]; + } + if ($gradeiteminstance->itemtype == 'course') { + $gradesarray['items']['course'] = $gradeitemarray; + $gradesarray['items']['course']['activityid'] = 'course'; + } else { + $gradesarray['items'][$modulecm->id] = $gradeitemarray; + // Add the activity ID as some WS clients can't access the array key. + $gradesarray['items'][$modulecm->id]['activityid'] = $modulecm->id; + } + } + + $gradesarray['outcomes'] = array(); + foreach ($grades->outcomes as $outcome) { + $modulecm = $cm; + if (empty($modulecm)) { + $modulecm = $acitivityinstances[$outcome->itemmodule][$outcome->iteminstance]; + } + $gradesarray['outcomes'][$modulecm->id] = (array)$outcome; + $gradesarray['outcomes'][$modulecm->id]['activityid'] = $modulecm->id; + + $gradesarray['outcomes'][$modulecm->id]['grades'] = array(); + if (!empty($outcome->grades)) { + foreach ($outcome->grades as $studentid => $studentgrade) { + if (!$canviewhidden) { + // Need to load the grade_grade object to check visibility. + $gradeiteminstance = self::get_grade_item( + $course->id, $outcome->itemtype, $outcome->itemmodule, $outcome->iteminstance, $outcome->itemnumber); + $gradegradeinstance = grade_grade::fetch( + array( + 'userid' => $studentid, + 'itemid' => $gradeiteminstance->id + ) + ); + // The grade grade may be legitimately missing if the student has no grade. + if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) { + continue; + } + } + $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid] = (array)$studentgrade; + + // Add the student ID into the grade structure as some WS clients can't access the key. + $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid]['userid'] = $studentid; + } + } + } + + return $gradesarray; + } + + /** + * Get a grade item + * @param int $courseid Course id + * @param string $itemtype Item type + * @param string $itemmodule Item module + * @param int $iteminstance Item instance + * @param int $itemnumber Item number + * @return grade_item A grade_item instance + */ + private static function get_grade_item($courseid, $itemtype, $itemmodule = null, $iteminstance = null, $itemnumber = null) { + $gradeiteminstance = null; + if ($itemtype == 'course') { + $gradeiteminstance = grade_item::fetch(array('courseid' => $courseid, 'itemtype' => $itemtype)); + } else { + $gradeiteminstance = grade_item::fetch( + array('courseid' => $courseid, 'itemtype' => $itemtype, + 'itemmodule' => $itemmodule, 'iteminstance' => $iteminstance, 'itemnumber' => $itemnumber)); + } + return $gradeiteminstance; + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 2.7 + */ + public static function get_grades_returns() { + return new external_single_structure( + array( + 'items' => new external_multiple_structure( + new external_single_structure( + array( + 'activityid' => new external_value( + PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), + 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), + 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), + 'name' => new external_value(PARAM_RAW, 'The module name'), + 'grademin' => new external_value(PARAM_FLOAT, 'Minimum grade'), + 'grademax' => new external_value(PARAM_FLOAT, 'Maximum grade'), + 'gradepass' => new external_value(PARAM_FLOAT, 'The passing grade threshold'), + 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), + 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'userid' => new external_value( + PARAM_INT, 'Student ID'), + 'grade' => new external_value( + PARAM_FLOAT, 'Student grade'), + 'locked' => new external_value( + PARAM_BOOL, 'Is the student\'s grade locked?'), + 'hidden' => new external_value( + PARAM_BOOL, 'Is the student\'s grade hidden?'), + 'overridden' => new external_value( + PARAM_BOOL, 'Is the student\'s grade overridden?'), + 'feedback' => new external_value( + PARAM_RAW, 'Feedback from the grader'), + 'feedbackformat' => new external_value( + PARAM_INT, 'The format of the feedback'), + 'usermodified' => new external_value( + PARAM_INT, 'The ID of the last user to modify this student grade'), + 'datesubmitted' => new external_value( + PARAM_INT, 'A timestamp indicating when the student submitted the activity'), + 'dategraded' => new external_value( + PARAM_INT, 'A timestamp indicating when the assignment was grades'), + 'str_grade' => new external_value( + PARAM_RAW, 'A string representation of the grade'), + 'str_long_grade' => new external_value( + PARAM_RAW, 'A nicely formatted string representation of the grade'), + 'str_feedback' => new external_value( + PARAM_TEXT, 'A string representation of the feedback from the grader'), + ) + ) + ), + ) + ) + ), + 'outcomes' => new external_multiple_structure( + new external_single_structure( + array( + 'activityid' => new external_value( + PARAM_ALPHANUM, 'The ID of the activity or "course" for the course grade item'), + 'itemnumber' => new external_value(PARAM_INT, 'Will be 0 unless the module has multiple grades'), + 'scaleid' => new external_value(PARAM_INT, 'The ID of the custom scale or 0'), + 'name' => new external_value(PARAM_RAW, 'The module name'), + 'locked' => new external_value(PARAM_BOOL, 'Is the grade item locked?'), + 'hidden' => new external_value(PARAM_BOOL, 'Is the grade item hidden?'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'userid' => new external_value( + PARAM_INT, 'Student ID'), + 'grade' => new external_value( + PARAM_FLOAT, 'Student grade'), + 'locked' => new external_value( + PARAM_BOOL, 'Is the student\'s grade locked?'), + 'hidden' => new external_value( + PARAM_BOOL, 'Is the student\'s grade hidden?'), + 'feedback' => new external_value( + PARAM_RAW, 'Feedback from the grader'), + 'feedbackformat' => new external_value( + PARAM_INT, 'The feedback format'), + 'usermodified' => new external_value( + PARAM_INT, 'The ID of the last user to modify this student grade'), + 'str_grade' => new external_value( + PARAM_RAW, 'A string representation of the grade'), + 'str_feedback' => new external_value( + PARAM_TEXT, 'A string representation of the feedback from the grader'), + ) + ) + ), + ) + ), 'An array of outcomes associated with the grade items', VALUE_OPTIONAL + ) + ) + ); + + } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 2.7 + */ + public static function update_grades_parameters() { + return new external_function_parameters( + array( + 'source' => new external_value(PARAM_TEXT, 'The source of the grade update'), + 'courseid' => new external_value(PARAM_INT, 'id of course'), + 'component' => new external_value(PARAM_COMPONENT, 'A component, for example mod_forum or mod_quiz'), + 'activityid' => new external_value(PARAM_INT, 'The activity ID'), + 'itemnumber' => new external_value( + PARAM_INT, 'grade item ID number for modules that have multiple grades. Typically this is 0.'), + 'grades' => new external_multiple_structure( + new external_single_structure( + array( + 'studentid' => new external_value(PARAM_INT, 'Student ID'), + 'grade' => new external_value(PARAM_FLOAT, 'Student grade'), + 'str_feedback' => new external_value( + PARAM_TEXT, 'A string representation of the feedback from the grader', VALUE_OPTIONAL), + ) + ), 'Any student grades to alter', VALUE_OPTIONAL), + 'itemdetails' => new external_single_structure( + array( + 'itemname' => new external_value( + PARAM_ALPHANUMEXT, 'The grade item name', VALUE_OPTIONAL), + 'idnumber' => new external_value( + PARAM_INT, 'Arbitrary ID provided by the module responsible for the grade item', VALUE_OPTIONAL), + 'gradetype' => new external_value( + PARAM_INT, 'The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)', VALUE_OPTIONAL), + 'grademax' => new external_value( + PARAM_FLOAT, 'Maximum grade allowed', VALUE_OPTIONAL), + 'grademin' => new external_value( + PARAM_FLOAT, 'Minimum grade allowed', VALUE_OPTIONAL), + 'scaleid' => new external_value( + PARAM_INT, 'The ID of the custom scale being is used', VALUE_OPTIONAL), + 'multfactor' => new external_value( + PARAM_FLOAT, 'Multiply all grades by this number', VALUE_OPTIONAL), + 'plusfactor' => new external_value( + PARAM_FLOAT, 'Add this to all grades', VALUE_OPTIONAL), + 'deleted' => new external_value( + PARAM_BOOL, 'True if the grade item should be deleted', VALUE_OPTIONAL), + 'hidden' => new external_value( + PARAM_BOOL, 'True if the grade item is hidden', VALUE_OPTIONAL), + ), 'Any grade item settings to alter', VALUE_OPTIONAL + ) + ) + ); + } + + /** + * Update a grade item and, optionally, student grades + * + * @param string $source The source of the grade update + * @param int $courseid The course id + * @param string $component Component name + * @param int $activityid The activity id + * @param int $itemnumber The item number + * @param array $grades Array of grades + * @param array $itemdetails Array of item details + * @return int A status flag + * @since Moodle 2.7 + */ + public static function update_grades($source, $courseid, $component, $activityid, + $itemnumber, $grades = array(), $itemdetails = array()) { + global $CFG; + + $params = self::validate_parameters( + self::update_grades_parameters(), + array( + 'source' => $source, + 'courseid' => $courseid, + 'component' => $component, + 'activityid' => $activityid, + 'itemnumber' => $itemnumber, + 'grades' => $grades, + 'itemdetails' => $itemdetails + ) + ); + + list($itemtype, $itemmodule) = normalize_component($params['component']); + + if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) { + throw new moodle_exception('invalidcoursemodule'); + } + $iteminstance = $cm->instance; + + $coursecontext = context_course::instance($params['courseid']); + + try { + self::validate_context($coursecontext); + } catch (Exception $e) { + $exceptionparam = new stdClass(); + $exceptionparam->message = $e->getMessage(); + $exceptionparam->courseid = $params['courseid']; + throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam); + } + + $hidinggrades = false; + $editinggradeitem = false; + $editinggrades = false; + + $gradestructure = array(); + foreach ($grades as $grade) { + $editinggrades = true; + $gradestructure[ $grade['studentid'] ] = array('userid' => $grade['studentid'], 'rawgrade' => $grade['grade']); + } + if (!empty($params['itemdetails'])) { + if (isset($params['itemdetails']['hidden'])) { + $hidinggrades = true; + } else { + $editinggradeitem = true; + } + } + + if ($editinggradeitem && !has_capability('moodle/grade:manage', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, + 'moodle/grade:manage required to edit grade information'); + } + if ($hidinggrades && !has_capability('moodle/grade:hide', $coursecontext) && + !has_capability('moodle/grade:hide', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, + 'moodle/grade:hide required to hide grade items'); + } + if ($editinggrades && !has_capability('moodle/grade:edit', $coursecontext)) { + throw new moodle_exception('nopermissiontoviewgrades', 'error', '', null, + 'moodle/grade:edit required to edit grades'); + } + + return grade_update($params['source'], $params['courseid'], $itemtype, + $itemmodule, $iteminstance, $itemnumber, $gradestructure, $params['itemdetails']); + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 2.7 + */ + public static function update_grades_returns() { + return new external_single_structure( + array ( + 'result' => new external_value( + PARAM_INT, + 'A value like ' . GRADE_UPDATE_OK . ' => OK, ' . GRADE_UPDATE_FAILED . ' => FAILED + as defined in lib/grade/constants.php') + ) + ); + } +} diff --git a/lib/db/services.php b/lib/db/services.php index e5a9642bf11..d93c067d552 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -104,7 +104,6 @@ $functions = array( 'core_grades_get_grades' => array( 'classname' => 'core_grades_external', 'methodname' => 'get_grades', - 'classpath' => 'grade/externallib.php', 'description' => 'Returns grade item details and optionally student grades.', 'type' => 'read', 'capabilities' => 'moodle/grade:view, moodle/grade:viewall', @@ -113,7 +112,6 @@ $functions = array( 'core_grades_update_grades' => array( 'classname' => 'core_grades_external', 'methodname' => 'update_grades', - 'classpath' => 'grade/externallib.php', 'description' => 'Update a grade item and associated student grades.', 'type' => 'write', 'capabilities' => '', diff --git a/grade/tests/externallib_grades_test.php b/lib/tests/grades_externallib_test.php similarity index 99% rename from grade/tests/externallib_grades_test.php rename to lib/tests/grades_externallib_test.php index 72663e6a5eb..866ac32b354 100644 --- a/grade/tests/externallib_grades_test.php +++ b/lib/tests/grades_externallib_test.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Unit tests for the grade API at /grade/externallib.php + * Unit tests for the grade API at /lib/classes/grades_external.php * * @package core_grades * @category external @@ -29,7 +29,6 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); -require_once($CFG->dirroot . '/grade/externallib.php'); /** * Grades functions unit tests