diff --git a/grade/edit/tree/grade.php b/grade/edit/tree/grade.php index 3545a647459..62536f65d98 100644 --- a/grade/edit/tree/grade.php +++ b/grade/edit/tree/grade.php @@ -197,17 +197,6 @@ if ($mform->is_cancelled()) { $data->feedbackformat = $old_grade_grade->feedbackformat; } - // Only log a grade override if they actually changed the student grade. - if ($data->finalgrade != $old_grade_grade->finalgrade) { - $url = '/report/grader/index.php?id=' . $course->id; - - $user = $DB->get_record('user', array('id'=>$data->userid), '*', MUST_EXIST); - $fullname = fullname($user); - - $info = "{$grade_item->itemname}: $fullname"; - add_to_log($course->id, 'grade', 'update', $url, $info); - } - // update final grade or feedback // when we set override grade the first time, it happens here $grade_item->update_final_grade($data->userid, $data->finalgrade, 'editgrade', $data->feedback, $data->feedbackformat); @@ -221,19 +210,6 @@ if ($mform->is_cancelled()) { $data->overridden = 0; // checkbox unticked } $grade_grade->set_overridden($data->overridden); - - if ($data->overridden == 0 && $data->overridden != $old_grade_grade->overridden) { - // Log removing an override. - // The addition of an override is logged above. - // One or the other will happen but never both. - $url = '/report/grader/index.php?id=' . $course->id; - - $user = $DB->get_record('user', array('id'=>$data->userid), '*', MUST_EXIST); - $fullname = fullname($user); - - $info = "{$grade_item->itemname}: $fullname"; - add_to_log($course->id, 'grade', 'update', $url, $info); - } } if (has_capability('moodle/grade:manage', $context) or has_capability('moodle/grade:hide', $context)) { @@ -289,6 +265,14 @@ if ($mform->is_cancelled()) { $grade_item->force_regrading(); } + $grade_grade = new grade_grade(array('userid'=>$data->userid, 'itemid'=>$grade_item->id), true); + if ($old_grade_grade->finalgrade != $grade_grade->finalgrade + or empty($old_grade_grade->overridden) != empty($grade_grade->overridden) + ) { + $grade_grade->grade_item = $grade_item; + \core\event\user_graded::create_from_grade($grade_grade)->trigger(); + } + redirect($returnurl); } diff --git a/grade/report/grader/ajax_callbacks.php b/grade/report/grader/ajax_callbacks.php index b61f579d04f..d49e26581f2 100644 --- a/grade/report/grader/ajax_callbacks.php +++ b/grade/report/grader/ajax_callbacks.php @@ -118,16 +118,10 @@ switch ($action) { echo json_encode($json_object); die(); } else { - $url = '/report/grader/index.php?id=' . $course->id; - - $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST); - $fullname = fullname($user); - - $info = "{$grade_item->itemname}: $fullname"; - add_to_log($course->id, 'grade', 'update', $url, $info); - $json_object->gradevalue = $finalvalue; + $old_grade_grade = new grade_grade(array('userid' => $userid, 'itemid' => $grade_item->id), true); + if ($grade_item->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE)) { $json_object->result = 'success'; $json_object->message = false; @@ -138,6 +132,14 @@ switch ($action) { die(); } + $grade_grade = new grade_grade(array('userid' => $userid, 'itemid' => $grade_item->id), true); + if ($old_grade_grade->finalgrade != $grade_grade->finalgrade + or empty($old_grade_grade->overridden) != empty($grade_grade->overridden) + ) { + $grade_grade->load_grade_item(); + \core\event\user_graded::create_from_grade($grade_grade)->trigger(); + } + // Get row data $sql = "SELECT gg.id, gi.id AS itemid, gi.scaleid AS scale, gg.userid AS userid, finalgrade, gg.overridden AS overridden " . "FROM {grade_grades} gg, {grade_items} gi WHERE " diff --git a/grade/report/grader/lib.php b/grade/report/grader/lib.php index f4a357ce906..4bf37081f1c 100644 --- a/grade/report/grader/lib.php +++ b/grade/report/grader/lib.php @@ -301,14 +301,19 @@ class grade_report_grader extends grade_report { } } - $url = '/report/grader/index.php?id=' . $this->course->id; - $fullname = fullname($this->users[$userid]); - - $info = "{$gradeitem->itemname}: $fullname"; - add_to_log($this->course->id, 'grade', 'update', $url, $info); + $oldgradegrade = new grade_grade(array('userid' => $userid, 'itemid' => $gradeitem->id), true); $gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE); + $gradegrade = new grade_grade(array('userid' => $userid, 'itemid' => $gradeitem->id), true); + + if ($oldgradegrade->finalgrade != $gradegrade->finalgrade + or empty($oldgradegrade->overridden) != empty($gradegrade->overridden) + ) { + $gradegrade->grade_item = $gradeitem; + \core\event\user_graded::create_from_grade($gradegrade)->trigger(); + } + // We can update feedback without reloading the grade item as it doesn't affect grade calculations if ($datatype === 'feedback') { $this->grades[$userid][$itemid]->feedback = $feedback; diff --git a/lang/en/grades.php b/lang/en/grades.php index 005f649a247..fc20033cf56 100644 --- a/lang/en/grades.php +++ b/lang/en/grades.php @@ -185,6 +185,7 @@ $string['errorupdatinggradecategoryaggregateoutcomes'] = 'Error updating the "In $string['errorupdatinggradecategoryaggregatesubcats'] = 'Error updating the "Aggregate including subcategories" setting of grade category ID {$a->id}'; $string['errorupdatinggradecategoryaggregation'] = 'Error updating the aggregation type of grade category ID {$a->id}'; $string['errorupdatinggradeitemaggregationcoef'] = 'Error updating the aggregation coefficient (weight or extra credit) of grade item ID {$a->id}'; +$string['eventusergraded'] = 'User grade edited in gradebook'; $string['excluded'] = 'Excluded'; $string['excluded_help'] = 'If ticked, the grade will not be included in any aggregation.'; $string['expand'] = 'Expand category'; diff --git a/lib/classes/event/user_graded.php b/lib/classes/event/user_graded.php new file mode 100644 index 00000000000..ae77611ba7b --- /dev/null +++ b/lib/classes/event/user_graded.php @@ -0,0 +1,135 @@ +. + +/** + * Grade edited event. + * + * @package core_grades + * @copyright 2014 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\event; +defined('MOODLE_INTERNAL') || die(); + +/** + * Event triggered after teacher edits manual grade or + * overrides activity/aggregated grade. + * + * Note: use grade_grades_history table if you need to know + * the history of grades. + * + * @property-read array $other Extra information about the event. + * -int itemid: grade item id. + * -bool overridden: Is this grade override? + * -float finalgrade: the final grade value. + * + * @package core_grades + * @copyright 2013 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_graded extends \core\event\base { + /** @var \grade_grade $grade */ + protected $grade; + + /** + * Utility method to create new event. + * + * @param \grade_grade $grade + * @return user_graded + */ + public static function create_from_grade(\grade_grade $grade) { + $event = self::create(array( + 'context' => \context_course::instance($grade->grade_item->courseid), + 'objectid' => $grade->id, + 'relateduserid' => $grade->userid, + 'other' => array( + 'itemid' => $grade->itemid, + 'overridden' => !empty($grade->overridden), + 'finalgrade' => $grade->finalgrade), + )); + $event->grade = $grade; + return $event; + } + + /** + * Get grade object. + * + * @return \grade_grade + */ + public function get_grade() { + if ($this->is_restored()) { + throw new \coding_exception('get_grade() is intended for event observers only'); + } + return $this->grade; + } + + /** + * Init method. + * + * @return void + */ + protected function init() { + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + $this->data['objecttable'] = 'grade_grades'; + } + + /** + * Return localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventusergraded', 'core_grades'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "User {$this->userid} edited grade of user {$this->objectid} for grade item " . $this->other['itemid']; + } + + /** + * Get URL related to the action + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/grade/edit/tree/grade.php', array( + 'courseid' => $this->courseid, + 'itemid' => $this->other['itemid'], + 'userid' => $this->relateduserid, + )); + } + + /** + * Return legacy log info. + * + * @return null|array of parameters to be passed to legacy add_to_log() function. + */ + public function get_legacy_logdata() { + $user = $this->get_record_snapshot('user', $this->relateduserid); + $fullname = fullname($user); + $info = $this->grade->grade_item->itemname . ': ' . $fullname; + $url = '/report/grader/index.php?id=' . $this->courseid; + + return array($this->courseid, 'grade', 'update', $url, $info); + } +} diff --git a/lib/tests/event_user_graded_test.php b/lib/tests/event_user_graded_test.php new file mode 100644 index 00000000000..293b00e1109 --- /dev/null +++ b/lib/tests/event_user_graded_test.php @@ -0,0 +1,89 @@ +. + +/** + * Tests for base course module viewed event. + * + * @package core + * @category phpunit + * @copyright 2014 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class core_event_user_graded_testcase + * + * Tests for event \core\event\user_graded + * + * @package core + * @category phpunit + * @copyright 2014 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_event_user_graded_testcase extends advanced_testcase { + /** + * Test the event. + */ + public function test_event() { + global $CFG; + require_once("$CFG->libdir/gradelib.php"); + + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $course->id); + + $grade_category = grade_category::fetch_course_category($course->id); + $grade_category->load_grade_item(); + $grade_item = $grade_category->grade_item; + + $grade_item->update_final_grade($user->id, 10, 'gradebook'); + + $grade_grade = new grade_grade(array('userid' => $user->id, 'itemid' => $grade_item->id), true); + $grade_grade->grade_item = $grade_item; + + $event = \core\event\user_graded::create_from_grade($grade_grade); + + $this->assertEventLegacyLogData( + array($course->id, 'grade', 'update', '/report/grader/index.php?id=' . $course->id, $grade_item->itemname . ': ' . fullname($user)), + $event + ); + $this->assertEquals(context_course::instance($course->id), $event->get_context()); + $this->assertSame($event->objecttable, 'grade_grades'); + $this->assertEquals($event->objectid, $grade_grade->id); + $this->assertEquals($event->other['itemid'], $grade_item->id); + $this->assertTrue($event->other['overridden']); + $this->assertEquals(10, $event->other['finalgrade']); + + // Trigger the events. + $sink = $this->redirectEvents(); + $event->trigger(); + $result = $sink->get_events(); + $sink->close(); + + $this->assertCount(1, $result); + + $event = reset($result); + $this->assertEventContextNotUsed($event); + + $grade = $event->get_grade(); + $this->assertInstanceOf('grade_grade', $grade); + $this->assertEquals($grade_grade->id, $grade->id); + } +}