/** * 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); }
/** * Factory method returning an instance of an assessment form * * @param moodle_url $actionurl URL of form handler, defaults to auto detect the current url * @param string $mode Mode to open the form in: preview/assessment/readonly * @param stdClass $assessment The current assessment * @param bool $editable * @param array $options */ public function get_assessment_form(moodle_url $actionurl = null, $mode = 'preview', stdclass $assessment = null, $editable = true, $options = array()) { global $CFG; // needed because the included files use it global $DB; require_once dirname(__FILE__) . '/assessment_form.php'; $fields = $this->prepare_form_fields($this->dimensions); $nodimensions = count($this->dimensions); // rewrite URLs to the embeded files for ($i = 0; $i < $nodimensions; $i++) { $fields->{'description__idx_' . $i} = file_rewrite_pluginfile_urls($fields->{'description__idx_' . $i}, 'pluginfile.php', $this->workshop->context->id, 'workshopform_rubric', 'description', $fields->{'dimensionid__idx_' . $i}); } if ('assessment' === $mode and !empty($assessment)) { // load the previously saved assessment data $grades = $this->get_current_assessment_data($assessment); $current = new stdclass(); for ($i = 0; $i < $nodimensions; $i++) { $dimid = $fields->{'dimensionid__idx_' . $i}; if (isset($grades[$dimid])) { $givengrade = $grades[$dimid]->grade; // find a level with this grade $levelid = null; foreach ($this->dimensions[$dimid]->levels as $level) { if (grade_floats_equal($level->grade, $givengrade)) { $levelid = $level->id; break; } } $current->{'gradeid__idx_' . $i} = $grades[$dimid]->id; $current->{'chosenlevelid__idx_' . $i} = $levelid; } } } // set up the required custom data common for all strategies $customdata['strategy'] = $this; $customdata['workshop'] = $this->workshop; $customdata['mode'] = $mode; $customdata['options'] = $options; // set up strategy-specific custom data $customdata['nodims'] = $nodimensions; $customdata['fields'] = $fields; $customdata['current'] = isset($current) ? $current : null; $attributes = array('class' => 'assessmentform rubric ' . $this->config->layout); $formclassname = 'workshop_rubric_' . $this->config->layout . '_assessment_form'; return new $formclassname($actionurl, $customdata, 'post', '', $attributes, $editable); }
/** * Set the flags on the grade_grade items to indicate how individual grades are used * in the aggregation. * * WARNING: This function is called a lot during gradebook recalculation, be very performance considerate. * * @param int $userid The user we have aggregated the grades for. * @param array $usedweights An array with keys for each of the grade_item columns included in the aggregation. The value are the relative weight. * @param array $novalue An array with keys for each of the grade_item columns skipped because * they had no value in the aggregation. * @param array $dropped An array with keys for each of the grade_item columns dropped * because of any drop lowest/highest settings in the aggregation. * @param array $extracredit An array with keys for each of the grade_item columns * considered extra credit by the aggregation. */ private function set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit) { global $DB; // We want to know all current user grades so we can decide whether they need to be updated or they already contain the // expected value. $sql = "SELECT gi.id, gg.aggregationstatus, gg.aggregationweight FROM {grade_grades} gg\n JOIN {grade_items} gi ON (gg.itemid = gi.id)\n WHERE gg.userid = :userid"; $params = array('categoryid' => $this->id, 'userid' => $userid); // These are all grade_item ids which grade_grades will NOT end up being 'unknown' (because they are not unknown or // because we will update them to something different that 'unknown'). $giids = array_keys($usedweights + $novalue + $dropped + $extracredit); if ($giids) { // We include grade items that might not be in categoryid. list($itemsql, $itemlist) = $DB->get_in_or_equal($giids, SQL_PARAMS_NAMED, 'gg'); $sql .= ' AND (gi.categoryid = :categoryid OR gi.id ' . $itemsql . ')'; $params = $params + $itemlist; } else { $sql .= ' AND gi.categoryid = :categoryid'; } $currentgrades = $DB->get_recordset_sql($sql, $params); // We will store here the grade_item ids that need to be updated on db. $toupdate = array(); if ($currentgrades->valid()) { // Iterate through the user grades to see if we really need to update any of them. foreach ($currentgrades as $currentgrade) { // Unset $usedweights that we do not need to update. if (!empty($usedweights) && isset($usedweights[$currentgrade->id]) && $currentgrade->aggregationstatus === 'used') { // We discard the ones that already have the contribution specified in $usedweights and are marked as 'used'. if (grade_floats_equal($currentgrade->aggregationweight, $usedweights[$currentgrade->id])) { unset($usedweights[$currentgrade->id]); } // Used weights can be present in multiple set_usedinaggregation arguments. if (!isset($novalue[$currentgrade->id]) && !isset($dropped[$currentgrade->id]) && !isset($extracredit[$currentgrade->id])) { continue; } } // No value grades. if (!empty($novalue) && isset($novalue[$currentgrade->id])) { if ($currentgrade->aggregationstatus !== 'novalue' || grade_floats_different($currentgrade->aggregationweight, 0)) { $toupdate['novalue'][] = $currentgrade->id; } continue; } // Dropped grades. if (!empty($dropped) && isset($dropped[$currentgrade->id])) { if ($currentgrade->aggregationstatus !== 'dropped' || grade_floats_different($currentgrade->aggregationweight, 0)) { $toupdate['dropped'][] = $currentgrade->id; } continue; } // Extra credit grades. if (!empty($extracredit) && isset($extracredit[$currentgrade->id])) { // If this grade item is already marked as 'extra' and it already has the provided $usedweights value would be // silly to update to 'used' to later update to 'extra'. if (!empty($usedweights) && isset($usedweights[$currentgrade->id]) && grade_floats_equal($currentgrade->aggregationweight, $usedweights[$currentgrade->id])) { unset($usedweights[$currentgrade->id]); } // Update the item to extra if it is not already marked as extra in the database or if the item's // aggregationweight will be updated when going through $usedweights items. if ($currentgrade->aggregationstatus !== 'extra' || !empty($usedweights) && isset($usedweights[$currentgrade->id])) { $toupdate['extracredit'][] = $currentgrade->id; } continue; } // If is not in any of the above groups it should be set to 'unknown', checking that the item is not already // unknown, if it is we don't need to update it. if ($currentgrade->aggregationstatus !== 'unknown' || grade_floats_different($currentgrade->aggregationweight, 0)) { $toupdate['unknown'][] = $currentgrade->id; } } $currentgrades->close(); } // Update items to 'unknown' status. if (!empty($toupdate['unknown'])) { list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['unknown'], SQL_PARAMS_NAMED, 'g'); $itemlist['userid'] = $userid; $sql = "UPDATE {grade_grades}\n SET aggregationstatus = 'unknown',\n aggregationweight = 0\n WHERE itemid {$itemsql} AND userid = :userid"; $DB->execute($sql, $itemlist); } // Update items to 'used' status and setting the proper weight. if (!empty($usedweights)) { // The usedweights items are updated individually to record the weights. foreach ($usedweights as $gradeitemid => $contribution) { $sql = "UPDATE {grade_grades}\n SET aggregationstatus = 'used',\n aggregationweight = :contribution\n WHERE itemid = :itemid AND userid = :userid"; $params = array('contribution' => $contribution, 'itemid' => $gradeitemid, 'userid' => $userid); $DB->execute($sql, $params); } } // Update items to 'novalue' status. if (!empty($toupdate['novalue'])) { list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['novalue'], SQL_PARAMS_NAMED, 'g'); $itemlist['userid'] = $userid; $sql = "UPDATE {grade_grades}\n SET aggregationstatus = 'novalue',\n aggregationweight = 0\n WHERE itemid {$itemsql} AND userid = :userid"; $DB->execute($sql, $itemlist); } // Update items to 'dropped' status. if (!empty($toupdate['dropped'])) { list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['dropped'], SQL_PARAMS_NAMED, 'g'); $itemlist['userid'] = $userid; $sql = "UPDATE {grade_grades}\n SET aggregationstatus = 'dropped',\n aggregationweight = 0\n WHERE itemid {$itemsql} AND userid = :userid"; $DB->execute($sql, $itemlist); } // Update items to 'extracredit' status. if (!empty($toupdate['extracredit'])) { list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['extracredit'], SQL_PARAMS_NAMED, 'g'); $itemlist['userid'] = $userid; $DB->set_field_select('grade_grades', 'aggregationstatus', 'extra', "itemid {$itemsql} AND userid = :userid", $itemlist); } }