/** * Converts <ELEMENT> into <workshopform_numerrors_dimension> and stores it for later writing * * @return array to be written to workshop.xml */ public function process_legacy_element($data, $raw) { $workshop = $this->parenthandler->get_current_workshop(); $mapping = array(); $mapping['id'] = $data['id']; $mapping['nonegative'] = $data['elementno']; if ($workshop['grade'] == 0 or $data['maxscore'] == 0) { $mapping['grade'] = 0; } else { $mapping['grade'] = grade_floatval($data['maxscore'] / $workshop['grade'] * 100); } $this->mappings[] = $mapping; $converted = null; if (trim($data['description']) and $data['description'] <> '@@ GRADE_MAPPING_ELEMENT @@') { // prepare a fake record and re-use the upgrade logic $fakerecord = (object)$data; $converted = (array)workshopform_numerrors_upgrade_element($fakerecord, 12345678); unset($converted['workshopid']); $converted['id'] = $data['id']; $this->dimensions[] = $converted; } return $converted; }
/** * Returns a grade 0.00000 to 100.00000 for the given number of errors * * This is where we use the mapping table defined by the teacher. If a grade for the given * number of errors (negative assertions) is not defined, the most recently defined one is used. * Example of the defined mapping: * Number of errors | Grade * 0 | 100% (always) * 1 | - (not defined) * 2 | 80% * 3 | 60% * 4 | - * 5 | 30% * 6 | 0% * With this mapping, one error is mapped to 100% grade and 4 errors is mapped to 60%. * * @param mixed $numerrors Number of errors * @return float Raw grade (0.00000 to 100.00000) for the given number of negative assertions */ protected function errors_to_grade($numerrors) { $grade = 100.00000; for ($i = 1; $i <= $numerrors; $i++) { if (isset($this->mappings[$i])) { $grade = $this->mappings[$i]->grade; } } if ($grade > 100.00000) { $grade = 100.00000; } if ($grade < 0.00000) { $grade = 0.00000; } return grade_floatval($grade); }
/** * Calculates the aggregated grade given by the reviewer * * @param array $grades Grade records as returned by {@link get_current_assessment_data} * @uses $this->dimensions * @return float|null Raw grade (from 0.00000 to 100.00000) for submission as suggested by the peer */ protected function calculate_peer_grade(array $grades) { if (empty($grades)) { return null; } // summarize the grades given in rubrics $sumgrades = 0; foreach ($grades as $grade) { $sumgrades += $grade->grade; } // get the minimal and maximal possible grade (sum of minimal/maximal grades across all dimensions) $mingrade = 0; $maxgrade = 0; foreach ($this->dimensions as $dimension) { $mindimensiongrade = null; $maxdimensiongrade = null; foreach ($dimension->levels as $level) { if (is_null($mindimensiongrade) or $level->grade < $mindimensiongrade) { $mindimensiongrade = $level->grade; } if (is_null($maxdimensiongrade) or $level->grade > $maxdimensiongrade) { $maxdimensiongrade = $level->grade; } } $mingrade += $mindimensiongrade; $maxgrade += $maxdimensiongrade; } if ($maxgrade - $mingrade > 0) { return grade_floatval(100 * ($sumgrades - $mingrade) / ($maxgrade - $mingrade)); } else { return null; } }
/** * Apply a grade from a grading form to a user (may be called multiple times for a group submission). * * @param stdClass $formdata - the data from the form * @param int $userid - the user to apply the grade to * @param int $attemptnumber - The attempt number to apply the grade to. * @return void */ protected function apply_grade_to_user($formdata, $userid, $attemptnumber) { global $USER, $CFG, $DB; $grade = $this->get_user_grade($userid, true, $attemptnumber); $gradingdisabled = $this->grading_disabled($userid); $gradinginstance = $this->get_grading_instance($userid, $grade, $gradingdisabled); if (!$gradingdisabled) { if ($gradinginstance) { $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); } else { // Handle the case when grade is set to No Grade. if (isset($formdata->grade)) { $grade->grade = grade_floatval(unformat_float($formdata->grade)); } } if (isset($formdata->workflowstate) || isset($formdata->allocatedmarker)) { $flags = $this->get_user_flags($userid, true); $flags->workflowstate = isset($formdata->workflowstate) ? $formdata->workflowstate : $flags->workflowstate; $flags->allocatedmarker = isset($formdata->allocatedmarker) ? $formdata->allocatedmarker : $flags->allocatedmarker; $this->update_user_flags($flags); } } $grade->grader = $USER->id; $adminconfig = $this->get_admin_config(); $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook; // Call save in plugins. foreach ($this->feedbackplugins as $plugin) { if ($plugin->is_enabled() && $plugin->is_visible()) { if (!$plugin->save($grade, $formdata)) { $result = false; print_error($plugin->get_error()); } if ('assignfeedback_' . $plugin->get_type() == $gradebookplugin) { // This is the feedback plugin chose to push comments to the gradebook. $grade->feedbacktext = $plugin->text_for_gradebook($grade); $grade->feedbackformat = $plugin->format_for_gradebook($grade); } } } $this->update_grade($grade); $this->notify_grade_modified($grade); $addtolog = $this->add_to_log('grade submission', $this->format_grade_for_log($grade), '', true); $params = array('context' => $this->context, 'objectid' => $grade->id, 'relateduserid' => $userid); $event = \mod_assign\event\submission_graded::create($params); $event->set_legacy_logdata($addtolog); $event->trigger(); }
/** * Apply a grade from a grading form to a user (may be called multiple times for a group submission) * * @param stdClass $formdata - the data from the form * @param int $userid - the user to apply the grade to * @return void */ private function apply_grade_to_user($formdata, $userid) { global $USER, $CFG, $DB; $grade = $this->get_user_grade($userid, true); $gradingdisabled = $this->grading_disabled($userid); $gradinginstance = $this->get_grading_instance($userid, $gradingdisabled); if (!$gradingdisabled) { if ($gradinginstance) { $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); } else { // Handle the case when grade is set to No Grade. if (isset($formdata->grade)) { $grade->grade= grade_floatval(unformat_float($formdata->grade)); } } } $grade->grader= $USER->id; $adminconfig = $this->get_admin_config(); $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook; // Call save in plugins. foreach ($this->feedbackplugins as $plugin) { if ($plugin->is_enabled() && $plugin->is_visible()) { if (!$plugin->save($grade, $formdata)) { $result = false; print_error($plugin->get_error()); } if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) { // This is the feedback plugin chose to push comments to the gradebook. $grade->feedbacktext = $plugin->text_for_gradebook($grade); $grade->feedbackformat = $plugin->format_for_gradebook($grade); } } } $this->update_grade($grade); $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); $this->add_to_log('grade submission', $this->format_grade_for_log($grade)); }
/** * Calculates the aggregated grade given by the reviewer * * @param array $grades Grade records as returned by {@link get_current_assessment_data} * @uses $this->dimensions * @return float|null Raw grade (from 0.00000 to 100.00000) for submission as suggested by the peer */ protected function calculate_peer_grade(array $grades) { if (empty($grades)) { return null; } $sumgrades = 0; $sumweights = 0; foreach ($grades as $grade) { $dimension = $this->dimensions[$grade->dimensionid]; if ($dimension->weight < 0) { throw new coding_exception('Negative weights are not supported any more. Something is wrong with your data'); } if (grade_floats_equal($dimension->weight, 0) or grade_floats_equal($dimension->grade, 0)) { // does not influence the final grade continue; } if ($dimension->grade < 0) { // this is a scale $scaleid = -$dimension->grade; $sumgrades += $this->scale_to_grade($scaleid, $grade->grade) * $dimension->weight * 100; $sumweights += $dimension->weight; } else { // regular grade $sumgrades += $grade->grade / $dimension->grade * $dimension->weight * 100; $sumweights += $dimension->weight; } } if ($sumweights === 0) { return 0; } return grade_floatval($sumgrades / $sumweights); }
/** * Compare two float numbers safely. Uses 5 decimals php precision. Nulls accepted too. * Used for skipping of db updates * @param float $f1 * @param float $f2 * @return true if different */ function grade_floats_different($f1, $f2) { // note: db rounding for 10,5 is different from php round() function return grade_floatval($f1) !== grade_floatval($f2); }
/** * In addition to update() as defined in grade_object, handle the grade_outcome and grade_scale objects. * Force regrading if necessary, rounds the float numbers using php function, * the reason is we need to compare the db value with computed number to skip regrading if possible. * @param string $source from where was the object inserted (mod/forum, manual, etc.) * @return boolean success */ function update($source = null) { // reset caches $this->dependson_cache = null; // Retrieve scale and infer grademax/min from it if needed $this->load_scale(); // make sure there is not 0 in outcomeid if (empty($this->outcomeid)) { $this->outcomeid = null; } if ($this->qualifies_for_regrading()) { $this->force_regrading(); } $this->timemodified = time(); $this->grademin = grade_floatval($this->grademin); $this->grademax = grade_floatval($this->grademax); $this->multfactor = grade_floatval($this->multfactor); $this->plusfactor = grade_floatval($this->plusfactor); $this->aggregationcoef = grade_floatval($this->aggregationcoef); return parent::update($source); }
/** * Recalculate the weights of the grade items in this category. * * The category total is not updated here, a further call to * {@link self::auto_update_max()} is required. * * @return void */ private function auto_update_weights() { global $CFG; if ($this->aggregation != GRADE_AGGREGATE_SUM) { // This is only required if we are using natural weights. return; } $children = $this->get_children(); $gradeitem = null; // Calculate the sum of the grademax's of all the items within this category. $totalnonoverriddengrademax = 0; $totalgrademax = 0; // Out of 1, how much weight has been manually overriden by a user? $totaloverriddenweight = 0; $totaloverriddengrademax = 0; // Has every assessment in this category been overridden? $automaticgradeitemspresent = false; // Does the grade item require normalising? $requiresnormalising = false; // This array keeps track of the id and weight of every grade item that has been overridden. $overridearray = array(); foreach ($children as $sortorder => $child) { $gradeitem = null; if ($child['type'] == 'item') { $gradeitem = $child['object']; } else { if ($child['type'] == 'category') { $gradeitem = $child['object']->load_grade_item(); } } if ($gradeitem->gradetype == GRADE_TYPE_NONE || $gradeitem->gradetype == GRADE_TYPE_TEXT) { // Text items and none items do not have a weight. continue; } else { if (!$this->aggregateoutcomes && $gradeitem->is_outcome_item()) { // We will not aggregate outcome items, so we can ignore them. continue; } else { if (empty($CFG->grade_includescalesinaggregation) && $gradeitem->gradetype == GRADE_TYPE_SCALE) { // The scales are not included in the aggregation, ignore them. continue; } } } // Record the ID and the weight for this grade item. $overridearray[$gradeitem->id] = array(); $overridearray[$gradeitem->id]['extracredit'] = intval($gradeitem->aggregationcoef); $overridearray[$gradeitem->id]['weight'] = $gradeitem->aggregationcoef2; $overridearray[$gradeitem->id]['weightoverride'] = intval($gradeitem->weightoverride); // If this item has had its weight overridden then set the flag to true, but // only if all previous items were also overridden. Note that extra credit items // are counted as overridden grade items. if (!$gradeitem->weightoverride && $gradeitem->aggregationcoef == 0) { $automaticgradeitemspresent = true; } if ($gradeitem->aggregationcoef > 0) { // An extra credit grade item doesn't contribute to $totaloverriddengrademax. continue; } else { if ($gradeitem->weightoverride > 0 && $gradeitem->aggregationcoef2 <= 0) { // An overriden item that defines a weight of 0 does not contribute to $totaloverriddengrademax. continue; } } $totalgrademax += $gradeitem->grademax; if ($gradeitem->weightoverride > 0) { $totaloverriddenweight += $gradeitem->aggregationcoef2; $totaloverriddengrademax += $gradeitem->grademax; } } // Initialise this variable (used to keep track of the weight override total). $normalisetotal = 0; // Keep a record of how much the override total is to see if it is above 100. It it is then we need to set the // other weights to zero and normalise the others. $overriddentotal = 0; // If the overridden weight total is higher than 1 then set the other untouched weights to zero. $setotherweightstozero = false; // Total up all of the weights. foreach ($overridearray as $gradeitemdetail) { // If the grade item has extra credit, then don't add it to the normalisetotal. if (!$gradeitemdetail['extracredit']) { $normalisetotal += $gradeitemdetail['weight']; } // The overridden total comprises of items that are set as overridden, that aren't extra credit and have a value // greater than zero. if ($gradeitemdetail['weightoverride'] && !$gradeitemdetail['extracredit'] && $gradeitemdetail['weight'] > 0) { // Add overriden weights up to see if they are greater than 1. $overriddentotal += $gradeitemdetail['weight']; } } if ($overriddentotal > 1) { // Make sure that this catergory of weights gets normalised. $requiresnormalising = true; // The normalised weights are only the overridden weights, so we just use the total of those. $normalisetotal = $overriddentotal; } $totalnonoverriddengrademax = $totalgrademax - $totaloverriddengrademax; // This setting indicates if we should use algorithm prior to MDL-49257 fix for calculating extra credit weights. // Even though old algorith has bugs in it, we need to preserve existing grades. $gradebookcalculationfreeze = (int) get_config('core', 'gradebook_calculations_freeze_' . $this->courseid); $oldextracreditcalculation = $gradebookcalculationfreeze && $gradebookcalculationfreeze <= 20150619; reset($children); foreach ($children as $sortorder => $child) { $gradeitem = null; if ($child['type'] == 'item') { $gradeitem = $child['object']; } else { if ($child['type'] == 'category') { $gradeitem = $child['object']->load_grade_item(); } } if ($gradeitem->gradetype == GRADE_TYPE_NONE || $gradeitem->gradetype == GRADE_TYPE_TEXT) { // Text items and none items do not have a weight, no need to set their weight to // zero as they must never be used during aggregation. continue; } else { if (!$this->aggregateoutcomes && $gradeitem->is_outcome_item()) { // We will not aggregate outcome items, so we can ignore updating their weights. continue; } else { if (empty($CFG->grade_includescalesinaggregation) && $gradeitem->gradetype == GRADE_TYPE_SCALE) { // We will not aggregate the scales, so we can ignore upating their weights. continue; } else { if (!$oldextracreditcalculation && $gradeitem->aggregationcoef > 0 && $gradeitem->weightoverride) { // For an item with extra credit ignore other weigths and overrides but do not change anything at all // if it's weight was already overridden. continue; } } } } // Store the previous value here, no need to update if it is the same value. $prevaggregationcoef2 = $gradeitem->aggregationcoef2; if (!$oldextracreditcalculation && $gradeitem->aggregationcoef > 0 && !$gradeitem->weightoverride) { // For an item with extra credit ignore other weigths and overrides. $gradeitem->aggregationcoef2 = $totalgrademax ? $gradeitem->grademax / $totalgrademax : 0; } else { if (!$gradeitem->weightoverride) { // Calculations with a grade maximum of zero will cause problems. Just set the weight to zero. if ($totaloverriddenweight >= 1 || $totalnonoverriddengrademax == 0 || $gradeitem->grademax == 0) { // There is no more weight to distribute. $gradeitem->aggregationcoef2 = 0; } else { // Calculate this item's weight as a percentage of the non-overridden total grade maxes // then convert it to a proportion of the available non-overriden weight. $gradeitem->aggregationcoef2 = $gradeitem->grademax / $totalnonoverriddengrademax * (1 - $totaloverriddenweight); } } else { if (!$automaticgradeitemspresent && $normalisetotal != 1 || $requiresnormalising || $overridearray[$gradeitem->id]['weight'] < 0) { // Just divide the overriden weight for this item against the total weight override of all // items in this category. if ($normalisetotal == 0 || $overridearray[$gradeitem->id]['weight'] < 0) { // If the normalised total equals zero, or the weight value is less than zero, // set the weight for the grade item to zero. $gradeitem->aggregationcoef2 = 0; } else { $gradeitem->aggregationcoef2 = $overridearray[$gradeitem->id]['weight'] / $normalisetotal; } } } } if (grade_floatval($prevaggregationcoef2) !== grade_floatval($gradeitem->aggregationcoef2)) { // Update the grade item to reflect these changes. $gradeitem->update(); } } }
public function test_calculate_peer_grade_two_scales_weighted() { global $DB; // fixture set-up $mockscale13 = 'Poor,Good,Excellent'; $mockscale17 = '-,*,**,***,****,*****,******'; $this->strategy->dimensions[1012] = (object) array('grade' => '-13', 'weight' => 2); $this->strategy->dimensions[1019] = (object) array('grade' => '-17', 'weight' => 3); $grades[] = (object) array('dimensionid' => 1012, 'grade' => '2.00000'); // "Good" $grades[] = (object) array('dimensionid' => 1019, 'grade' => '5.00000'); // "****" $DB->expectAt(0, 'get_field', array('scale', 'scale', array('id' => 13), MUST_EXIST)); $DB->setReturnValueAt(0, 'get_field', $mockscale13); $DB->expectAt(1, 'get_field', array('scale', 'scale', array('id' => 17), MUST_EXIST)); $DB->setReturnValueAt(1, 'get_field', $mockscale17); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEqual(grade_floatval((1 / 2 * 2 + 4 / 6 * 3) / 5 * 100), $suggested); }
/** * Check if there are some legacy workshop 1.x data to be migrated and upgrade them * * This must be called after workshop core migration has finished so that * all assessments are already upgraded and tables are correctly renamed. */ function workshopform_numerrors_upgrade_legacy() { global $CFG, $DB, $OUTPUT; require_once($CFG->dirroot . '/mod/workshop/db/upgradelib.php'); if (!workshopform_numerrors_upgrade_legacy_needed()) { return; } // get the list of all legacy workshops using this grading strategy if ($legacyworkshops = $DB->get_records('workshop_old', array('gradingstrategy' => 2), 'course,id', 'id')) { echo $OUTPUT->notification('Copying assessment forms elements and grade mappings', 'notifysuccess'); $legacyworkshops = array_keys($legacyworkshops); // get some needed info about the workshops $workshopinfos = $DB->get_records_list('workshop_old', 'id', $legacyworkshops, 'id', 'id,grade'); // get the list of all form elements list($workshopids, $params) = $DB->get_in_or_equal($legacyworkshops, SQL_PARAMS_NAMED); $sql = "SELECT * FROM {workshop_elements_old} WHERE workshopid $workshopids AND newid IS NULL"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $old) { // process the information about mapping $newmapping = new stdclass(); $newmapping->workshopid = $old->workshopid; $newmapping->nonegative = $old->elementno; $newmapping->grade = $old->maxscore; if ($old->maxscore > 0) { $newmapping->grade = grade_floatval($old->maxscore / $workshopinfos[$old->workshopid]->grade * 100); } else { $newmapping->grade = 0; } $DB->delete_records('workshopform_numerrors_map', array('workshopid' => $newmapping->workshopid, 'nonegative' => $newmapping->nonegative)); $DB->insert_record('workshopform_numerrors_map', $newmapping); // process the information about the element itself if (trim($old->description) and $old->description <> '@@ GRADE_MAPPING_ELEMENT @@') { $new = workshopform_numerrors_upgrade_element($old, $old->workshopid); $newid = $DB->insert_record('workshopform_numerrors', $new); } else { $newid = 0; } $DB->set_field('workshop_elements_old', 'newplugin', 'numerrors', array('id' => $old->id)); $DB->set_field('workshop_elements_old', 'newid', $newid, array('id' => $old->id)); } $rs->close(); // now we need to reload the legacy ids. Although we have them in $newelements after the first run, we must // refetch them from DB so that this function can be called during recovery $newelementids = workshop_upgrade_element_id_mappings('numerrors'); // migrate all grades for these elements (it est the values that reviewers put into forms) echo $OUTPUT->notification('Copying assessment form grades', 'notifysuccess'); $sql = "SELECT * FROM {workshop_grades_old} WHERE workshopid $workshopids AND newid IS NULL"; $rs = $DB->get_recordset_sql($sql, $params); $newassessmentids = workshop_upgrade_assessment_id_mappings(); foreach ($rs as $old) { if (!isset($newassessmentids[$old->assessmentid])) { // orphaned grade - the assessment was removed but the grade remained continue; } if (!isset($newelementids[$old->workshopid]) or !isset($newelementids[$old->workshopid][$old->elementno])) { // orphaned grade - the assessment form element has been removed after the grade was recorded continue; } $newelementinfo = $newelementids[$old->workshopid][$old->elementno]; if ($newelementinfo->newid == 0 or $old->feedback == '@@ GRADE_ADJUSTMENT @@') { // this is not a real grade - it was used just for mapping purposes $DB->set_field('workshop_grades_old', 'newplugin', 'numerrors_map', array('id' => $old->id)); $DB->set_field('workshop_grades_old', 'newid', 0, array('id' => $old->id)); continue; } $new = workshopform_numerrors_upgrade_grade($old, $newassessmentids[$old->assessmentid], $newelementids[$old->workshopid][$old->elementno]); $newid = $DB->insert_record('workshop_grades', $new); $DB->set_field('workshop_grades_old', 'newplugin', 'numerrors', array('id' => $old->id)); $DB->set_field('workshop_grades_old', 'newid', $newid, array('id' => $old->id)); } $rs->close(); } }
private function normalize_grade($dim,$grade) { //todo: weight? is weight a factor here? probably should be... $dimmin = $dim->min; $dimmax = $dim->max; if ($dimmin == $dimmax) { return grade_floatval($dimmax); } else { return grade_floatval(($grade - $dimmin) / ($dimmax - $dimmin) * 100); } }
/** * Update workshop grades in the gradebook * * Needed by grade_update_mod_grades() in lib/gradelib.php * * @category grade * @param stdClass $workshop instance object with extra cmidnumber and modname property * @param int $userid update grade of specific user only, 0 means all participants * @return void */ function workshop_update_grades(stdclass $workshop, $userid=0) { global $CFG, $DB; require_once($CFG->libdir.'/gradelib.php'); //todo: this ignores userid if($workshop->teammode) { //this is necessary because we need data like the grouping id $course = $DB->get_record('course', array('id' => $workshop->course), '*', MUST_EXIST); $cm = get_coursemodule_from_instance('workshop', $workshop->id, $course->id, false, MUST_EXIST); $whereuser = ''; if ($userid) { $groups = groups_get_all_groups($cm->course, $userid, $cm->groupingid); if(count($groups) == 1) $group = $groups[0]; $whereuser = $DB->get_in_or_equal(array_keys(groups_get_members($group,'u.id',''))); } else { $allgroups = groups_get_all_groups($cm->course, 0, $cm->groupingid); //todo: on duplicate key error out $groupmembers = $DB->get_records_list('groups_members','groupid',array_keys($allgroups),'','userid,groupid'); //invert this array for use later $membergroups = array(); foreach($groupmembers as $i) { $membergroups[$i->groupid][] = $i->userid; } } $params = array('workshopid' => $workshop->id, 'userid' => $userid); $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded FROM {workshop_submissions} WHERE workshopid = :workshopid AND example=0' . $whereuser . ' ORDER BY timemodified DESC'; $records = $DB->get_records_sql($sql, $params); $submissions = array(); //this hinges on ORDER BY timemodified DESC if ( isset($allgroups) ) { foreach($records as $r) { $grp = $groupmembers[$r->authorid]->groupid; if (isset($submissions[$grp])) continue; $submissions[$grp] = $r; } } // print_r($submissions); foreach($submissions as $grp => $s) { $members = $membergroups[$grp]; foreach($members as $m) { $grade = new stdclass(); $grade->userid = $m; if (!is_null($s->gradeover)) { $grade->rawgrade = grade_floatval($workshop->grade * $s->gradeover / 100); $grade->usermodified = $s->gradeoverby; } else { $grade->rawgrade = grade_floatval($workshop->grade * $s->grade / 100); } $grade->feedback = $s->feedbackauthor; $grade->feedbackformat = $s->feedbackauthorformat; $grade->datesubmitted = $s->timemodified; $grade->dategraded = $s->timegraded; $submissiongrades[$m] = $grade; } } } else { $whereuser = $userid ? ' AND authorid = :userid' : ''; $params = array('workshopid' => $workshop->id, 'userid' => $userid); $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded FROM {workshop_submissions} WHERE workshopid = :workshopid AND example=0' . $whereuser; $records = $DB->get_records_sql($sql, $params); $submissiongrades = array(); foreach ($records as $record) { $grade = new stdclass(); $grade->userid = $record->authorid; if (!is_null($record->gradeover)) { $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100); $grade->usermodified = $record->gradeoverby; } else { $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100); } $grade->feedback = $record->feedbackauthor; $grade->feedbackformat = $record->feedbackauthorformat; $grade->datesubmitted = $record->timemodified; $grade->dategraded = $record->timegraded; $submissiongrades[$record->authorid] = $grade; } } $whereuser = $userid ? ' AND userid = :userid' : ''; $params = array('workshopid' => $workshop->id, 'userid' => $userid); $sql = 'SELECT userid, gradinggrade, timegraded FROM {workshop_aggregations} WHERE workshopid = :workshopid' . $whereuser; $records = $DB->get_records_sql($sql, $params); $assessmentgrades = array(); foreach ($records as $record) { $grade = new stdclass(); $grade->userid = $record->userid; $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100); $grade->dategraded = $record->timegraded; $assessmentgrades[$record->userid] = $grade; } workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades); }
/** * In addition to update() as defined in grade_object rounds the float numbers using php function, * the reason is we need to compare the db value with computed number to skip updates if possible. * * @param string $source from where was the object inserted (mod/forum, manual, etc.) * @return bool success */ public function update($source = null) { $this->rawgrade = grade_floatval($this->rawgrade); $this->finalgrade = grade_floatval($this->finalgrade); $this->rawgrademin = grade_floatval($this->rawgrademin); $this->rawgrademax = grade_floatval($this->rawgrademax); return parent::update($source); }
public function test_average_assessment_different_weights() { // fixture set-up $assessments = array(); $assessments[11] = (object) array('weight' => 1, 'dimgrades' => array(3 => 10.0, 4 => 13.4, 5 => 95.0)); $assessments[13] = (object) array('weight' => 3, 'dimgrades' => array(3 => 11.0, 4 => 10.1, 5 => 92.0)); $assessments[17] = (object) array('weight' => 1, 'dimgrades' => array(3 => 11.0, 4 => 8.1, 5 => 88.0)); // exercise SUT $average = $this->evaluator->average_assessment($assessments); // validate $this->assertEquals(gettype($average->dimgrades), 'array'); $this->assertEquals(grade_floatval($average->dimgrades[3]), grade_floatval((10.0 + 11.0 * 3 + 11.0) / 5)); $this->assertEquals(grade_floatval($average->dimgrades[4]), grade_floatval((13.4 + 10.1 * 3 + 8.1) / 5)); $this->assertEquals(grade_floatval($average->dimgrades[5]), grade_floatval((95.0 + 92.0 * 3 + 88.0) / 5)); }
/** * Update workshop grades in the gradebook * * Needed by grade_update_mod_grades() in lib/gradelib.php * * @category grade * @param stdClass $workshop instance object with extra cmidnumber and modname property * @param int $userid update grade of specific user only, 0 means all participants * @return void */ function workshop_update_grades(stdclass $workshop, $userid=0) { global $CFG, $DB; require_once($CFG->libdir.'/gradelib.php'); $whereuser = $userid ? ' AND authorid = :userid' : ''; $params = array('workshopid' => $workshop->id, 'userid' => $userid); $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded FROM {workshop_submissions} WHERE workshopid = :workshopid AND example=0' . $whereuser; $records = $DB->get_records_sql($sql, $params); $submissiongrades = array(); foreach ($records as $record) { $grade = new stdclass(); $grade->userid = $record->authorid; if (!is_null($record->gradeover)) { $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100); $grade->usermodified = $record->gradeoverby; } else { $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100); } $grade->feedback = $record->feedbackauthor; $grade->feedbackformat = $record->feedbackauthorformat; $grade->datesubmitted = $record->timemodified; $grade->dategraded = $record->timegraded; $submissiongrades[$record->authorid] = $grade; } $whereuser = $userid ? ' AND userid = :userid' : ''; $params = array('workshopid' => $workshop->id, 'userid' => $userid); $sql = 'SELECT userid, gradinggrade, timegraded FROM {workshop_aggregations} WHERE workshopid = :workshopid' . $whereuser; $records = $DB->get_records_sql($sql, $params); $assessmentgrades = array(); foreach ($records as $record) { $grade = new stdclass(); $grade->userid = $record->userid; $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100); $grade->dategraded = $record->timegraded; $assessmentgrades[$record->userid] = $grade; } workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades); }
/** * Given a record from workshop_assessments_old, returns record to be stored in workshop_assessment * * @param stdClass $old record from workshop_assessments_old, * @param int $newsubmissionid new submission id * @param array $legacyteachers (int)userid => notused the list of legacy workshop teachers for the submission's workshop * @param int $legacyteacherweight weight of teacher's assessment in legacy workshop * @return stdClass */ function workshop_upgrade_transform_assessment(stdClass $old, $newsubmissionid, array $legacyteachers, $legacyteacherweight) { global $CFG; require_once $CFG->libdir . '/gradelib.php'; $new = new stdclass(); $new->submissionid = $newsubmissionid; $new->reviewerid = $old->userid; if (isset($legacyteachers[$old->userid])) { $new->weight = $legacyteacherweight; } else { $new->weight = 1; } if ($old->grade < 0) { // in workshop 1.x, this is just allocated assessment that has not been touched yet, having timecreated one year in the future :-/ $new->timecreated = time(); } else { $new->grade = grade_floatval($old->grade); if ($old->teachergraded) { $new->gradinggradeover = grade_floatval($old->gradinggrade); } else { $new->gradinggrade = grade_floatval($old->gradinggrade); } $new->feedbackauthor = $old->generalcomment; $new->feedbackauthorformat = FORMAT_HTML; $new->feedbackreviewer = $old->teachercomment; $new->feedbackreviewerformat = FORMAT_HTML; $new->timecreated = $old->timecreated; $new->timemodified = $old->timegraded; } return $new; }
/** * Given a set of a submission's assessments, returns a hypothetical average assessment * * The passed structure must be array of assessments objects with ->weight and ->dimgrades properties. * * @param array $assessments as prepared by {@link self::prepare_data_from_recordset()} * @return null|stdClass */ protected function average_assessment(array $assessments) { $sumdimgrades = array(); foreach ($assessments as $a) { foreach ($a->dimgrades as $dimid => $dimgrade) { if (!isset($sumdimgrades[$dimid])) { $sumdimgrades[$dimid] = 0; } $sumdimgrades[$dimid] += $dimgrade * $a->weight; } } $sumweights = 0; foreach ($assessments as $a) { $sumweights += $a->weight; } if ($sumweights == 0) { // unable to calculate average assessment return null; } $average = new stdclass(); $average->dimgrades = array(); foreach ($sumdimgrades as $dimid => $sumdimgrade) { $average->dimgrades[$dimid] = grade_floatval($sumdimgrade / $sumweights); } return $average; }
public function validation($data, $files) { $errors = parent::validation($data, $files); // Check open and close times are consistent. if ($data['timeopen'] != 0 && $data['timeclose'] != 0 && $data['timeclose'] < $data['timeopen']) { $errors['timeclose'] = get_string('closebeforeopen', 'quiz'); } // Check that the grace period is not too short. if ($data['overduehandling'] == 'graceperiod') { $graceperiodmin = get_config('quiz', 'graceperiodmin'); if ($data['graceperiod'] <= $graceperiodmin) { $errors['graceperiod'] = get_string('graceperiodtoosmall', 'quiz', format_time($graceperiodmin)); } } if (array_key_exists('completion', $data) && $data['completion'] == COMPLETION_TRACKING_AUTOMATIC) { $completionpass = isset($data['completionpass']) ? $data['completionpass'] : $this->current->completionpass; // Show an error if require passing grade was selected and the grade to pass was set to 0. if ($completionpass && (empty($data['gradepass']) || grade_floatval($data['gradepass']) == 0)) { if (isset($data['completionpass'])) { $errors['completionpassgroup'] = get_string('gradetopassnotset', 'quiz'); } else { $errors['gradepass'] = get_string('gradetopassmustbeset', 'quiz'); } } } // Check the boundary value is a number or a percentage, and in range. $i = 0; while (!empty($data['feedbackboundaries'][$i])) { $boundary = trim($data['feedbackboundaries'][$i]); if (strlen($boundary) > 0) { if ($boundary[strlen($boundary) - 1] == '%') { $boundary = trim(substr($boundary, 0, -1)); if (is_numeric($boundary)) { $boundary = $boundary * $data['grade'] / 100.0; } else { $errors["feedbackboundaries[{$i}]"] = get_string('feedbackerrorboundaryformat', 'quiz', $i + 1); } } else { if (!is_numeric($boundary)) { $errors["feedbackboundaries[{$i}]"] = get_string('feedbackerrorboundaryformat', 'quiz', $i + 1); } } } if (is_numeric($boundary) && $boundary <= 0 || $boundary >= $data['grade']) { $errors["feedbackboundaries[{$i}]"] = get_string('feedbackerrorboundaryoutofrange', 'quiz', $i + 1); } if (is_numeric($boundary) && $i > 0 && $boundary >= $data['feedbackboundaries'][$i - 1]) { $errors["feedbackboundaries[{$i}]"] = get_string('feedbackerrororder', 'quiz', $i + 1); } $data['feedbackboundaries'][$i] = $boundary; $i += 1; } $numboundaries = $i; // Check there is nothing in the remaining unused fields. if (!empty($data['feedbackboundaries'])) { for ($i = $numboundaries; $i < count($data['feedbackboundaries']); $i += 1) { if (!empty($data['feedbackboundaries'][$i]) && trim($data['feedbackboundaries'][$i]) != '') { $errors["feedbackboundaries[{$i}]"] = get_string('feedbackerrorjunkinboundary', 'quiz', $i + 1); } } } for ($i = $numboundaries + 1; $i < count($data['feedbacktext']); $i += 1) { if (!empty($data['feedbacktext'][$i]['text']) && trim($data['feedbacktext'][$i]['text']) != '') { $errors["feedbacktext[{$i}]"] = get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1); } } // Any other rule plugins. $errors = quiz_access_manager::validate_settings_form_fields($errors, $data, $files, $this); return $errors; }
/** * Compare two float numbers safely. Uses 5 decimals php precision using {@link grade_floatval()} * * Do not use rounding for 10,5 at the database level as the results may be * different from php round() function. * * @since 2.0 * @param float $f1 Float one to compare * @param float $f2 Float two to compare * @return bool True if the values should be considered as the same grades */ function grade_floats_equal($f1, $f2) { return grade_floatval($f1) === grade_floatval($f2); }
/** * save grade * * @param moodleform $mform * @return bool - was the grade saved */ private function process_save_grade(&$mform) { global $USER, $DB, $CFG; // Include grade form require_once $CFG->dirroot . '/mod/assign/gradeform.php'; // Need submit permission to submit an assignment require_capability('mod/assign:grade', $this->context); require_sesskey(); $rownum = required_param('rownum', PARAM_INT); $useridlist = optional_param('useridlist', '', PARAM_TEXT); if ($useridlist) { $useridlist = explode(',', $useridlist); } else { $useridlist = $this->get_grading_userid_list(); } $last = false; $userid = $useridlist[$rownum]; if ($rownum == count($useridlist) - 1) { $last = true; } $data = new stdClass(); $mform = new mod_assign_grade_form(null, array($this, $data, array('rownum' => $rownum, 'useridlist' => $useridlist, 'last' => false)), 'post', '', array('class' => 'gradeform')); if ($formdata = $mform->get_data()) { $grade = $this->get_user_grade($userid, true); $gradingdisabled = $this->grading_disabled($userid); $gradinginstance = $this->get_grading_instance($userid, $gradingdisabled); if (!$gradingdisabled) { if ($gradinginstance) { $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); } else { // handle the case when grade is set to No Grade if (isset($formdata->grade)) { $grade->grade = grade_floatval(unformat_float($formdata->grade)); } } } $grade->grader = $USER->id; $adminconfig = $this->get_admin_config(); $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook; // call save in plugins foreach ($this->feedbackplugins as $plugin) { if ($plugin->is_enabled() && $plugin->is_visible()) { if (!$plugin->save($grade, $formdata)) { $result = false; print_error($plugin->get_error()); } if ('assignfeedback_' . $plugin->get_type() == $gradebookplugin) { // this is the feedback plugin chose to push comments to the gradebook $grade->feedbacktext = $plugin->text_for_gradebook($grade); $grade->feedbackformat = $plugin->format_for_gradebook($grade); } } } $this->process_outcomes($userid, $formdata); $grade->mailed = 0; $this->update_grade($grade); $this->notify_grade_modified($grade); $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); $this->add_to_log('grade submission', $this->format_grade_for_log($grade)); } else { return false; } return true; }
/** * Given an array of all assessments done by a single reviewer, calculates the final grading grade * * This calculates the simple mean of the passed grading grades. If, however, the grading grade * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored. * * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade) * @param null|int $timegraded explicit timestamp of the aggregation, defaults to the current time * @return void */ protected function aggregate_grading_grades_process(array $assessments, $timegraded = null) { global $DB; $reviewerid = null; // the id of the reviewer being processed $current = null; // the gradinggrade currently saved in database $finalgrade = null; // the new grade to be calculated $agid = null; // aggregation id $sumgrades = 0; $count = 0; if (is_null($timegraded)) { $timegraded = time(); } foreach ($assessments as $assessment) { if (is_null($reviewerid)) { // the id is the same in all records, fetch it during the first loop cycle $reviewerid = $assessment->reviewerid; } if (is_null($agid)) { // the id is the same in all records, fetch it during the first loop cycle $agid = $assessment->aggregationid; } if (is_null($current)) { // the currently saved grade is the same in all records, fetch it during the first loop cycle $current = $assessment->aggregatedgrade; } if (!is_null($assessment->gradinggradeover)) { // the grading grade for this assessment is overridden by a teacher $sumgrades += $assessment->gradinggradeover; $count++; } else { if (!is_null($assessment->gradinggrade)) { $sumgrades += $assessment->gradinggrade; $count++; } } } if ($count > 0) { $finalgrade = grade_floatval($sumgrades / $count); } // Event information. $params = array('context' => $this->context, 'courseid' => $this->course->id, 'relateduserid' => $reviewerid); // check if the new final grade differs from the one stored in the database if (grade_floats_different($finalgrade, $current)) { $params['other'] = array('currentgrade' => $current, 'finalgrade' => $finalgrade); // we need to save new calculation into the database if (is_null($agid)) { // no aggregation record yet $record = new stdclass(); $record->workshopid = $this->id; $record->userid = $reviewerid; $record->gradinggrade = $finalgrade; $record->timegraded = $timegraded; $record->id = $DB->insert_record('workshop_aggregations', $record); $params['objectid'] = $record->id; $event = \mod_workshop\event\assessment_evaluated::create($params); $event->trigger(); } else { $record = new stdclass(); $record->id = $agid; $record->gradinggrade = $finalgrade; $record->timegraded = $timegraded; $DB->update_record('workshop_aggregations', $record); $params['objectid'] = $agid; $event = \mod_workshop\event\assessment_reevaluated::create($params); $event->trigger(); } } }
/** * Apply a grade from a grading form to a user (may be called multiple times for a group submission). * * @param stdClass $formdata - the data from the form * @param int $userid - the user to apply the grade to * @param int attemptnumber - The attempt number to apply the grade to. * @return void */ protected function apply_grade_to_user($formdata, $userid, $attemptnumber) { global $USER, $CFG, $DB; $grade = $this->get_user_grade($userid, true, $attemptnumber); $gradingdisabled = $this->grading_disabled($userid); $gradinginstance = $this->get_grading_instance($userid, $grade, $gradingdisabled); if (!$gradingdisabled) { if ($gradinginstance) { $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); } else { // Handle the case when grade is set to No Grade. if (isset($formdata->grade)) { $grade->grade = grade_floatval(unformat_float($formdata->grade)); } } } $grade->grader= $USER->id; $adminconfig = $this->get_admin_config(); $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook; // Call save in plugins. foreach ($this->feedbackplugins as $plugin) { if ($plugin->is_enabled() && $plugin->is_visible()) { if (!$plugin->save($grade, $formdata)) { $result = false; print_error($plugin->get_error()); } if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) { // This is the feedback plugin chose to push comments to the gradebook. $grade->feedbacktext = $plugin->text_for_gradebook($grade); $grade->feedbackformat = $plugin->format_for_gradebook($grade); } } } $this->update_grade($grade); // Note the default if not provided for this option is true (e.g. webservices). // This is for backwards compatibility. if (!isset($formdata->sendstudentnotifications) || $formdata->sendstudentnotifications) { $this->notify_grade_modified($grade); } $this->add_to_log('grade submission', $this->format_grade_for_log($grade)); }
/** * Apply a grade from a grading form to a user (may be called multiple times for a group submission). * * @param stdClass $formdata - the data from the form * @param int $userid - the user to apply the grade to * @param int $attemptnumber - The attempt number to apply the grade to. * @return void */ protected function apply_grade_to_user($formdata, $userid, $attemptnumber) { global $USER, $CFG, $DB; $grade = $this->get_user_grade($userid, true, $attemptnumber); $originalgrade = $grade->grade; $gradingdisabled = $this->grading_disabled($userid); $gradinginstance = $this->get_grading_instance($userid, $grade, $gradingdisabled); if (!$gradingdisabled) { if ($gradinginstance) { $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); } else { // Handle the case when grade is set to No Grade. if (isset($formdata->grade)) { $grade->grade = grade_floatval(unformat_float($formdata->grade)); } } if (isset($formdata->workflowstate) || isset($formdata->allocatedmarker)) { $flags = $this->get_user_flags($userid, true); $oldworkflowstate = $flags->workflowstate; $flags->workflowstate = isset($formdata->workflowstate) ? $formdata->workflowstate : $flags->workflowstate; $flags->allocatedmarker = isset($formdata->allocatedmarker) ? $formdata->allocatedmarker : $flags->allocatedmarker; if ($this->update_user_flags($flags) && isset($formdata->workflowstate) && $formdata->workflowstate !== $oldworkflowstate) { $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); \mod_assign\event\workflow_state_updated::create_from_user($this, $user, $formdata->workflowstate)->trigger(); } } } $grade->grader = $USER->id; $adminconfig = $this->get_admin_config(); $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook; // Call save in plugins. foreach ($this->feedbackplugins as $plugin) { if ($plugin->is_enabled() && $plugin->is_visible()) { if (!$plugin->save($grade, $formdata)) { $result = false; print_error($plugin->get_error()); } if ('assignfeedback_' . $plugin->get_type() == $gradebookplugin) { // This is the feedback plugin chose to push comments to the gradebook. $grade->feedbacktext = $plugin->text_for_gradebook($grade); $grade->feedbackformat = $plugin->format_for_gradebook($grade); } } } // We do not want to update the timemodified if no grade was added. if (!empty($formdata->addattempt) || $originalgrade !== null && $originalgrade != -1 || $grade->grade !== null && $grade->grade != -1) { $this->update_grade($grade, !empty($formdata->addattempt)); } // Note the default if not provided for this option is true (e.g. webservices). // This is for backwards compatibility. if (!isset($formdata->sendstudentnotifications) || $formdata->sendstudentnotifications) { $this->notify_grade_modified($grade, true); } }
public function test_calculate_peer_grade_something() { // fixture set-up $grades[6] = (object) array('dimensionid' => 6, 'grade' => 2); $grades[8] = (object) array('dimensionid' => 8, 'grade' => 2); $grades[10] = (object) array('dimensionid' => 10, 'grade' => 30); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate // minimal rubric score is 10, maximal is 51. We have 34 here $this->assertEquals(grade_floatval($suggested), grade_floatval(100 * 24 / 41)); }
/** * Given an array of all assessments done by a single reviewer, calculates the final grading grade * * This calculates the simple mean of the passed grading grades. If, however, the grading grade * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored. * * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade) * @return void */ protected function aggregate_grading_grades_process(array $assessments) { global $DB; $reviewerid = null; // the id of the reviewer being processed $current = null; // the gradinggrade currently saved in database $finalgrade = null; // the new grade to be calculated $agid = null; // aggregation id $sumgrades = 0; $count = 0; foreach ($assessments as $assessment) { if (is_null($reviewerid)) { // the id is the same in all records, fetch it during the first loop cycle $reviewerid = $assessment->reviewerid; } if (is_null($agid)) { // the id is the same in all records, fetch it during the first loop cycle $agid = $assessment->aggregationid; } if (is_null($current)) { // the currently saved grade is the same in all records, fetch it during the first loop cycle $current = $assessment->aggregatedgrade; } if (!is_null($assessment->gradinggradeover)) { // the grading grade for this assessment is overridden by a teacher $sumgrades += $assessment->gradinggradeover; $count++; } else { if (!is_null($assessment->gradinggrade)) { $sumgrades += $assessment->gradinggrade; $count++; } } } if ($count > 0) { $finalgrade = grade_floatval($sumgrades / $count); } // check if the new final grade differs from the one stored in the database if (grade_floats_different($finalgrade, $current)) { // we need to save new calculation into the database if (is_null($agid)) { // no aggregation record yet $record = new stdclass(); $record->workshopid = $this->id; $record->userid = $reviewerid; $record->gradinggrade = $finalgrade; $record->timegraded = time(); $DB->insert_record('workshop_aggregations', $record); } else { $record = new stdclass(); $record->id = $agid; $record->gradinggrade = $finalgrade; $record->timegraded = time(); $DB->update_record('workshop_aggregations', $record); } } }
/** * Update workshopplus grades in the gradebook * * Needed by grade_update_mod_grades() in lib/gradelib.php * * @category grade * @param stdClass $workshopplus instance object with extra cmidnumber and modname property * @param int $userid update grade of specific user only, 0 means all participants * @return void */ function workshopplus_update_grades(stdclass $workshopplus, $userid = 0) { global $CFG, $DB; require_once $CFG->libdir . '/gradelib.php'; $whereuser = $userid ? ' AND authorid = :userid' : ''; $params = array('workshopplusid' => $workshopplus->id, 'userid' => $userid); $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded FROM {workshopplus_submissions} WHERE workshopplusid = :workshopplusid AND example=0' . $whereuser; $records = $DB->get_records_sql($sql, $params); $submissiongrades = array(); foreach ($records as $record) { $grade = new stdclass(); $grade->userid = $record->authorid; if (!is_null($record->gradeover)) { $grade->rawgrade = grade_floatval($workshopplus->grade * $record->gradeover / 100); $grade->usermodified = $record->gradeoverby; } else { $grade->rawgrade = grade_floatval($workshopplus->grade * $record->grade / 100); } $grade->feedback = $record->feedbackauthor; $grade->feedbackformat = $record->feedbackauthorformat; $grade->datesubmitted = $record->timemodified; $grade->dategraded = $record->timegraded; $submissiongrades[$record->authorid] = $grade; ////// Find the groupmates and also update their records ////////////By Morteza /// find the group id of the current user $usergroups = groups_get_user_groups($workshopplus->course, $grade->userid); $currentgroupid = $usergroups[0][0]; // Get the current group name from the group id. $currentgroupname = groups_get_group_name($currentgroupid); // loop over members of that group $groupmembers = groups_get_members($currentgroupid, $fields = 'u.*'); /* Commenting out section for blind copy of grades * -- Sayantan - 28.04.2015 foreach ($groupmembers as $memberid=>$member){ if ($memberid != $grade->userid) { $newgrade = clone $grade; $newgrade->userid = $memberid; $submissiongrades[$memberid] = $newgrade; } } */ //////////end by Morteza /* * Begin: Change by Sayantan * Date: 28.04.2015 */ // Also think of scenario where author belonged to another group - Not possible unless students are // allowed to resubmit their solutions - have to think about this one // Check if this assignment has been graded previously (entry exists in grade_grades) // no -> this is a new grading, simply copy grade of author to the group members // yes -> this is a regrading $params = array('workshopplusid' => $workshopplus->id, 'userid' => $grade->userid); $sql = 'SELECT COUNT(1) AS cnt FROM moodle_grade_grades grades ' . 'INNER JOIN moodle_grade_items items ' . 'WHERE items.id = grades.itemid AND items.iteminstance = :workshopplusid AND grades.userid=:userid'; $records = $DB->get_records_sql($sql, $params); $flag_re_grading = 0; foreach ($records as $record) { if ($record->cnt > 0) { // Check if records exist in grade_grades $flag_re_grading = 1; // If records exist in grade_grades then this is a regrading } } if ($flag_re_grading == 0) { // This is a new grading, hence copy grades foreach ($groupmembers as $memberid => $member) { if ($memberid != $grade->userid) { $newgrade = clone $grade; $newgrade->userid = $memberid; $submissiongrades[$memberid] = $newgrade; } } } else { // This is a re grading, hence existing grades should not be overwritten // + Check the time in which each member joined the group // + If the time of joining group is later than time of previous grading then member was in a // separate group when the initial grading was done and should get the grades he/she received // in that group // + If the member has no previous records - assign zero grade since this assignment was not // done by that member (as he was not part of any group when the assignment was first graded) // foreach ($groupmembers as $memberid => $member) { if ($memberid != $grade->userid) { // Check time at which $memberid joined the group $joining_time = 0; $params = array('userid' => $memberid); $sql = 'SELECT timeadded FROM moodle_groups_members WHERE userid=:userid'; $records = $DB->get_records_sql($sql, $params); foreach ($records as $record) { $joining_time = $record->timeadded; } // Check time of previous grading $grading_modified_time = 0; $params = array('workshopplusid' => $workshopplus->id, 'userid' => $memberid); $sql = 'SELECT grades.timemodified FROM moodle_grade_grades grades INNER JOIN moodle_grade_items items WHERE items.id = grades.itemid AND items.iteminstance=:workshopplusid AND grades.userid=:userid'; $records = $DB->get_records_sql($sql, $params); // If no records are fetched then for the particular group member there exist no records in grades // This means that when the previous grading was done this member was not part of any group // and the grade assigned should be 0 if (sizeof($records) == 0) { $newgrade = clone $grade; $newgrade->userid = $memberid; $grade->rawgrade = 0; // Grade assigned to zero since this member was not present when previous grading was done $submissiongrades[$memberid] = $newgrade; } else { foreach ($records as $record) { $grading_modified_time = $record->timemodified; } } // If records exist for the member in grades, and time of joining group is before time of previous grading // then a simple copy of grades from the author's grades is okay if (sizeof($records) < 0 && $grading_modified_time > $joining_time) { $newgrade = clone $grade; // Copy grade since member was in authors group earlier also $newgrade->userid = $memberid; $submissiongrades[$memberid] = $newgrade; } // If record exists for the member in grade but time of joining group is after time of last grading // then we need to restore the previous grade that this member achieved if (sizeof($records) < 0 && $grading_modified_time < $joining_time) { // This member has already received a grade for this assignment // but the member was in another group // hence leave the previous grade untouched // do not add this grade in the array $submissiongrades // Thus in the grades table, the old grade for this member will remain unchanged // Need to recalculate grades from the workshopplus submission of previous group // the member belonged to // Check the group history table // Find a group with max(timeadded) < $grading_modified_time $params = array('grading_modified_time' => $grading_modified_time, 'userid' => $memberid); $sql = 'SELECT groupid FROM moodle_groups_members_history a WHERE userid=:userid AND timeadded=(SELECT MAX(timeadded) FROM moodle_groups_members_history b WHERE a.userid=b.userid AND a.groupid=b.groupid AND b.timeadded<:grading_modified_time)'; $records = $DB->get_records_sql($sql, $params); // Find members of this group foreach ($records as $record) { $groupid = $records->groupid; } // loop over members of that group $groupmembers = groups_get_members($groupid, $fields = 'u.*'); // Iterate over members to find submission for the given workshopplus id foreach ($groupmembers as $oldmemberid => $oldmember) { // Find submission for this group in workshopplus_submissions $oldparams = array('workshopplusid' => $workshopplus->id, 'userid' => $oldmemberid); $oldsql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded FROM {workshopplus_submissions} WHERE workshopplusid = :workshopplusid AND example=0 AND authorid=:userid ORDER BY timemodified DESC'; $oldrecords = $DB->get_records_sql($oldsql, $oldparams); // Break whenever a submission is found for a member if (sizeof($records != 0)) { // Copy grade for this user $oldgrade = new stdclass(); $oldgrade->userid = $memberid; if (!is_null($oldrecords->gradeover)) { $oldgrade->rawgrade = grade_floatval($workshopplus->grade * $oldrecords->gradeover / 100); $oldgrade->usermodified = $oldrecords->gradeoverby; } else { $oldgrade->rawgrade = grade_floatval($workshopplus->grade * $oldrecords->grade / 100); } $oldgrade->feedback = $oldrecords->feedbackauthor; $oldgrade->feedbackformat = $oldrecords->feedbackauthorformat; $oldgrade->datesubmitted = $oldrecords->timemodified; $oldgrade->dategraded = $oldrecords->timegraded; $submissiongrades[$memberid] = $oldgrade; break; } } } } } } /* * End: Change by Sayantan * Date: 28.04.2015 */ } // Updating assessment grades -- only comment added -- no change to code $whereuser = $userid ? ' AND userid = :userid' : ''; $params = array('workshopplusid' => $workshopplus->id, 'userid' => $userid); $sql = 'SELECT userid, gradinggrade, timegraded FROM {workshopplus_aggregations} WHERE workshopplusid = :workshopplusid' . $whereuser; $records = $DB->get_records_sql($sql, $params); $assessmentgrades = array(); foreach ($records as $record) { $grade = new stdclass(); $grade->userid = $record->userid; $grade->rawgrade = grade_floatval($workshopplus->gradinggrade * $record->gradinggrade / 100); $grade->dategraded = $record->timegraded; $assessmentgrades[$record->userid] = $grade; } workshopplus_grade_item_update($workshopplus, $submissiongrades, $assessmentgrades); }
public function test_calculate_peer_grade_two_scales_weighted() { $this->resetAfterTest(true); // fixture set-up $scale13 = $this->getDataGenerator()->create_scale(array('scale' => 'Poor,Good,Excellent', 'id' => 13)); $scale17 = $this->getDataGenerator()->create_scale(array('scale' => '-,*,**,***,****,*****,******', 'id' => 17)); $this->strategy->dimensions[1012] = (object) array('grade' => -$scale13->id, 'weight' => 2); $this->strategy->dimensions[1019] = (object) array('grade' => -$scale17->id, 'weight' => 3); $grades[] = (object) array('dimensionid' => 1012, 'grade' => '2.00000'); // "Good" $grades[] = (object) array('dimensionid' => 1019, 'grade' => '5.00000'); // "****" // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(grade_floatval((1 / 2 * 2 + 4 / 6 * 3) / 5 * 100), $suggested); }
public static function get_final_grade_by_course_id($courseId, $roleId) { // no MOODLE não temos factories // obtemos os acessores diretamente via variaveis globais global $CFG; require_once $CFG->dirroot . '/grade/lib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; require_once $CFG->dirroot . '/user/lib.php'; // para trabalhar com as grades de notas precisa-se ter // as seguintes habilidades: // * moodle/grade:export // * gradeexprt/txt:view // primeiro pego o contexto do sistema com base no usuário corrente $context = get_context_instance(CONTEXT_SYSTEM); // então verifico as abilidades uma a uma require_capability('moodle/grade:export', $context); require_capability('gradeexport/txt:view', $context); // neste ponto se verifica os parametros informados estão corretos // e dentro do exigido pela função // observe que solicitei os parametros como sendo do tipo inteiro // diretamente sem que sejam algum tipo de estrutura. Isto facilita // o acesso aos parametros, $funcParams = self::validate_parameters(self::get_final_grade_by_course_id_parameters(), array('courseId' => $courseId, 'roleId' => $roleId)); if (MDEBUG) { mDebug_log($funcParams, 'Parametros da Função'); } $usersIds = array(); $context = get_context_instance(CONTEXT_COURSE, $funcParams['courseId']); $role = new stdClass(); $role->id = $roleId; $users = get_users_from_role_on_context($role, $context); if (MDEBUG) { mDebug_log($users); } $usersIds = array(); foreach ($users as $user) { $usersIds[] = $user->userid; } if (MDEBUG) { mDebug_log($usersIds, 'Ids dos Usuários'); } // OK, agora que está tudo ok, consulto no banco de dados a nota // do usuário conforme o curso $grades = grade_get_course_grades($funcParams['courseId'], $usersIds); if (MDEBUG) { mDebug_log($grades, 'Grades'); } // TODO, estudar melhorias neste retorno. foreach ($grades->grades as $userId => $grade) { $result = array(); if ($grade === false) { $result['grade'] = grade_floatval(-9999); } else { if ($grade === null) { $result['grade'] = grade_floatval(-8888); } else { $result['grade'] = grade_floatval($grade->grade); } } $result['userId'] = $userId; $results[] = $result; } if (MDEBUG) { mDebug_log($results, 'Grades finais a serem enviadas'); } return $results; }