function validation($data, $files) { $errors = parent::validation($data, $files); $mform =& $this->_form; // check the calculation formula if ($data['calculation'] != '') { $grade_item = grade_item::fetch(array('id' => $data['id'], 'courseid' => $data['courseid'])); $calculation = calc_formula::unlocalize(stripslashes($data['calculation'])); $result = $grade_item->validate_formula($calculation); if ($result !== true) { $errors['calculation'] = $result; } } return $errors; }
function validation($data) { $errors = array(); $mform =& $this->_form; // check the calculation formula if ($data['calculation'] != '') { $grade_item = grade_item::fetch(array('id' => $data['id'], 'courseid' => $data['courseid'])); $calculation = calc_formula::unlocalize(stripslashes($data['calculation'])); $result = $grade_item->validate_formula($calculation); if ($result !== true) { $errors['calculation'] = $result; } } if (0 == count($errors)) { return true; } else { return $errors; } }
error('Incorect item id'); } // activity items and items without grade can not have calculation if ($grade_item->is_external_item() or $grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) { redirect($returnurl, get_string('errornocalculationallowed', 'grades')); } $mform = new edit_calculation_form(null, array('gpr' => $gpr, 'itemid' => $grade_item->id)); if ($mform->is_cancelled()) { redirect($returnurl); } $calculation = calc_formula::localize($grade_item->calculation); $calculation = grade_item::denormalize_formula($calculation, $grade_item->courseid); $mform->set_data(array('courseid' => $grade_item->courseid, 'calculation' => $calculation, 'id' => $grade_item->id, 'itemname' => $grade_item->itemname)); $errors = array(); if ($data = $mform->get_data(false)) { $calculation = calc_formula::unlocalize($data->calculation); $grade_item->set_calculation($calculation); redirect($returnurl); } elseif (!empty($section) and $section = 'idnumbers' and !empty($idnumbers)) { // Handle idnumbers separately (non-mform) //first validate and store the new idnumbers foreach ($idnumbers as $giid => $value) { if ($gi = grade_item::fetch(array('id' => $giid))) { if ($gi->itemtype == 'mod') { $cm = get_coursemodule_from_instance($gi->itemmodule, $gi->iteminstance, $gi->courseid); } else { $cm = null; } if (!grade_verify_idnumber($value, $COURSE->id, $gi, $cm)) { $errors[$giid] = get_string('idnumbertaken'); continue;
public function test_scientific_notation() { $formula = new calc_formula('=10e10'); $this->assertWithinMargin($formula->evaluate(), 100000000000.0, 100000000000.0 * 1.0E-15); $formula = new calc_formula('=10e-10'); $this->assertWithinMargin($formula->evaluate(), 1.0E-9, 100000000000.0 * 1.0E-15); $formula = new calc_formula('=10e+10'); $this->assertWithinMargin($formula->evaluate(), 100000000000.0, 100000000000.0 * 1.0E-15); $formula = new calc_formula('=10e10*5'); $this->assertWithinMargin($formula->evaluate(), 500000000000.0, 100000000000.0 * 1.0E-15); $formula = new calc_formula('=10e10^2'); $this->assertWithinMargin($formula->evaluate(), 1.0E+22, 1.0E+22 * 1.0E-15); }
/** * Validate the formula. * @param string $formula * @return boolean true if calculation possible, false otherwise */ function validate_formula($formulastr) { global $CFG; require_once $CFG->libdir . '/mathslib.php'; $formulastr = grade_item::normalize_formula($formulastr, $this->courseid); if (empty($formulastr)) { return true; } if (strpos($formulastr, '=') !== 0) { return get_string('errorcalculationnoequal', 'grades'); } // get used items if (preg_match_all('/##gi(\\d+)##/', $formulastr, $matches)) { $useditems = array_unique($matches[1]); // remove duplicates } else { $useditems = array(); } // MDL-11902 // unset the value if formula is trying to reference to itself // but array keys does not match itemid if (!empty($this->id)) { $useditems = array_diff($useditems, array($this->id)); //unset($useditems[$this->id]); } // prepare formula and init maths library $formula = preg_replace('/##(gi\\d+)##/', '\\1', $formulastr); $formula = new calc_formula($formula); if (empty($useditems)) { $grade_items = array(); } else { $gis = implode(',', $useditems); $sql = "SELECT gi.*\n FROM {$CFG->prefix}grade_items gi\n WHERE gi.id IN ({$gis}) and gi.courseid={$this->courseid}"; // from the same course only! if (!($grade_items = get_records_sql($sql))) { $grade_items = array(); } } $params = array(); foreach ($useditems as $itemid) { // make sure all grade items exist in this course if (!array_key_exists($itemid, $grade_items)) { return false; } // use max grade when testing formula, this should be ok in 99.9% // division by 0 is one of possible problems $params['gi' . $grade_items[$itemid]->id] = $grade_items[$itemid]->grademax; } // do the calculation $formula->set_params($params); $result = $formula->evaluate(); // false as result indicates some problem if ($result === false) { // TODO: add more error hints return get_string('errorcalculationunknown', 'grades'); } else { return true; } }
public function test_rand_float() { $formula = new calc_formula('=rand_float()'); $result = $formula->evaluate(); $this->assertTrue(is_float($result)); }
/** * Tests special chars */ function test__specialchars() { $formula = new calc_formula('=gi1 + gi2 + gi11', array('gi1' => 10, 'gi2' => 20, 'gi11' => 30)); $res = $formula->evaluate(); $this->assertEqual($res, 60, 'sum is: %s'); }
/** * */ protected function process_calculations($text) { global $CFG; // HACK removing occurences of [[entryid]] because they are // currently not resolved in new entries. $text = str_replace('[[entryid]]', '', $text); if (preg_match_all("/%%F\\d*:=[^%]*%%/", $text, $matches)) { require_once "{$CFG->libdir}/mathslib.php"; sort($matches[0]); // List of formulas. $formulas = array(); // Formula replacements. $replacements = array(); // Register all formulas according to formula identifier. foreach ($matches[0] as $pattern) { $cleanpattern = trim($pattern, '%'); list($fid, $formula) = explode(':=', $cleanpattern, 2); // Skip an empty formula. if (empty($formula) and $formula !== 0) { continue; } isset($formulas[$fid]) or $formulas[$fid] = array(); // Enclose formula in brackets to preserve precedence. $formulas[$fid][] = "({$formula})"; $replacements[$pattern] = $formula; } // Process group formulas in formulas (e.g. _F1_). // We put 0 if we can't find a replacement so that the formula could be calculated. foreach ($formulas as $fid => $formulae) { foreach ($formulae as $key => $formula) { if (preg_match_all("/_F\\d+_/", $formula, $frefs)) { foreach ($frefs[0] as $fref) { $fref = trim($fref, '_'); if (isset($formulas[$fref])) { $replace = implode(',', $formulas[$fref]); } else { $replace = 0; } $formula = str_replace("_{$fref}_", $replace, $formula); } $formulae[$key] = $formula; } } $formulas[$fid] = $formulae; } // Process group formulas in replacements (e.g. _F1_). // We put 0 if we can't find a replacement so that the formula could be calculated. foreach ($replacements as $pattern => $formula) { if (preg_match_all("/_F\\d+_/", $formula, $frefs)) { foreach ($frefs[0] as $fref) { $fref = trim($fref, '_'); if (isset($formulas[$fref])) { $replace = implode(',', $formulas[$fref]); } else { $replace = 0; } $formula = str_replace("_{$fref}_", $replace, $formula); } $replacements[$pattern] = $formula; } } // Calculate. foreach ($replacements as $pattern => $formula) { // Number of decimals can be set as ;n at the end of the formula. $decimals = null; if (strpos($formula, ';')) { list($formula, $decimals) = explode(';', $formula); } $calc = new \calc_formula("={$formula}"); $result = $calc->evaluate(); if ($result === false) { // False as result indicates some problem. // We remove the formula altogether. $replacements[$pattern] = ''; } else { // Set decimals. if (is_numeric($decimals)) { $result = sprintf("%4.{$decimals}f", $result); } $replacements[$pattern] = $result; } } $text = str_replace(array_keys($replacements), $replacements, $text); } return $text; }
/** * Returns user's calculated grades for the specified grade item. * * @param grade_item $gradeitem * @param array $userids The user ids whose grades should be retrieved. * @return array|null */ protected function get_user_grades_calculated($gradeitem, array $userids) { global $CFG; if (!($df = $this->df)) { return null; } require_once "{$CFG->libdir}/mathslib.php"; $formula = $gradeitem->gradecalc; // Patterns container. $patterns = array(); // Users container. $users = array_fill_keys($userids, array()); // Grades container. $grades = array(); foreach ($users as $userid => $unused) { $grades[$userid] = (object) array('id' => $userid, 'userid' => $userid, 'rawgrade' => null); // Num entries pattern. if (strpos($formula, '##numentries##') !== false) { $patterns['##numentries##'] = 0; if ($numentries = $df->get_entries_count_per_user($df::COUNT_ALL, $userid)) { foreach ($numentries as $userid => $count) { $users[$userid]['##numentries##'] = $count->numentries; } } } // Extract grading field patterns from the formula. if (preg_match_all("/##\\d*:[^#]+##/", $formula, $matches)) { // Get the entry ids per user. $entryids = $df->get_entry_ids_per_user($userid); foreach ($matches[0] as $pattern) { $patterns[$pattern] = 0; list($targetval, $fieldpattern) = explode(':', trim($pattern, '#'), 2); // Get the field from the pattern. if (!($field = $df->field_manager->get_field_by_pattern("[[{$fieldpattern}]]"))) { continue; } $uservalues = null; // Get user values for the pattern. // The field must either has helper\contentperuser component, // or be an instance of interface grading. $helper = "dataformfield_{$field->type}\\helper\\contentperuser"; if (class_exists($helper)) { $uservalues = $helper::get_content($field, $fieldpattern, $entryids); } else { if ($field instanceof mod_dataform\interfaces\grading) { // BC - this method for grading user values is depracated. $uservalues = $field->get_user_values($fieldpattern, $entryids, $userid); } } // Leave pattern value at 0 if no user values. if (!$uservalues) { continue; } // Register pattern values for users. foreach ($uservalues as $userid => $values) { // Keep only target val if specified. if ($targetval) { foreach ($values as $key => $value) { if ($value != $targetval) { unset($values[$key]); } } } if ($values) { $users[$userid][$pattern] = implode(',', $values); } } } } } // For each user calculate the formula and create a grade object. foreach ($grades as $userid => $grade) { // If no values, no grade for this user. if (empty($users[$userid])) { continue; } $values = $users[$userid]; $replacements = array_merge($patterns, $values); $calculation = str_replace(array_keys($replacements), $replacements, $formula); $calc = new calc_formula("={$calculation}"); $result = $calc->evaluate(); // False as result indicates some problem. if ($result !== false) { $grade->rawgrade = $result; } } return $grades; }
public function test_scientific_notation() { $formula = new calc_formula('=10e10'); $this->assertEquals($formula->evaluate(), 1e11, '', 1e11*1e-15); $formula = new calc_formula('=10e-10'); $this->assertEquals($formula->evaluate(), 1e-9, '', 1e11*1e-15); $formula = new calc_formula('=10e+10'); $this->assertEquals($formula->evaluate(), 1e11, '', 1e11*1e-15); $formula = new calc_formula('=10e10*5'); $this->assertEquals($formula->evaluate(), 5e11, '', 1e11*1e-15); $formula = new calc_formula('=10e10^2'); $this->assertEquals($formula->evaluate(), 1e22, '', 1e22*1e-15); }
/** * Tests that the event is fired in the correct locations in core. */ public function test_event_is_triggered() { global $DB; // Create the items we need to test with. $course = $this->getDataGenerator()->create_course(); $user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id); $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id)); $quizitemparams = array('itemtype' => 'mod', 'itemmodule' => 'quiz', 'iteminstance' => $quiz->id, 'courseid' => $course->id); $gradeitem = grade_item::fetch($quizitemparams); $courseitem = grade_item::fetch_course_item($course->id); // Now mark the quiz using grade_update as this is the function that modules use. $grade = array(); $grade['userid'] = $user->id; $grade['rawgrade'] = 60; $sink = $this->redirectEvents(); grade_update('mod/quiz', $course->id, 'mod', 'quiz', $quiz->id, 0, $grade); $events = $sink->get_events(); $sink->close(); // Ensure we have two user_graded events, one for the item, one for the course. $this->assertEquals(2, count($events)); $this->assertInstanceOf('\\core\\event\\user_graded', $events[0]); $this->assertEquals($gradeitem->id, $events[0]->other['itemid']); $this->assertInstanceOf('\\core\\event\\user_graded', $events[1]); $this->assertEquals($courseitem->id, $events[1]->other['itemid']); // Remove the grades, force the regrading and re-fetch the item. This is needed because the item // will be set as needing an update when the grades are deleted. $gradeitem->delete_all_grades(); grade_regrade_final_grades($course->id); $gradeitem = grade_item::fetch($quizitemparams); // Now, create a grade using grade_item::update_final_grade(). $sink = $this->redirectEvents(); $gradeitem->update_raw_grade($user->id, 10); $events = $sink->get_events(); $sink->close(); // Ensure we have two user_graded events, one for the item, one for the course. $this->assertEquals(2, count($events)); $this->assertInstanceOf('\\core\\event\\user_graded', $events[0]); $this->assertEquals($gradeitem->id, $events[0]->other['itemid']); $this->assertInstanceOf('\\core\\event\\user_graded', $events[1]); $this->assertEquals($courseitem->id, $events[1]->other['itemid']); // Now, update this grade using grade_item::update_raw_grade(). $sink = $this->redirectEvents(); $gradeitem->update_raw_grade($user->id, 20); $events = $sink->get_events(); $sink->close(); // Ensure we have two user_graded events, one for the item, one for the course. $this->assertEquals(2, count($events)); $this->assertInstanceOf('\\core\\event\\user_graded', $events[0]); $this->assertEquals($gradeitem->id, $events[0]->other['itemid']); $this->assertInstanceOf('\\core\\event\\user_graded', $events[1]); $this->assertEquals($courseitem->id, $events[1]->other['itemid']); // Remove the grades, force the regrading and re-fetch the item. This is needed because the item // will be set as needing an update when the grades are deleted. $gradeitem->delete_all_grades(); grade_regrade_final_grades($course->id); $gradeitem = grade_item::fetch($quizitemparams); // Now, create a grade using grade_item::update_final_grade(). $sink = $this->redirectEvents(); $gradeitem->update_final_grade($user->id, 30); $events = $sink->get_events(); $sink->close(); // Ensure we have two user_graded events, one for the item, one for the course. $this->assertEquals(2, count($events)); $this->assertInstanceOf('\\core\\event\\user_graded', $events[0]); $this->assertEquals($gradeitem->id, $events[0]->other['itemid']); $this->assertInstanceOf('\\core\\event\\user_graded', $events[1]); $this->assertEquals($courseitem->id, $events[1]->other['itemid']); // Now, update this grade using grade_item::update_final_grade(). $sink = $this->redirectEvents(); $gradeitem->update_final_grade($user->id, 40); $events = $sink->get_events(); $sink->close(); // Ensure we have two user_graded events, one for the item, one for the course. $this->assertEquals(2, count($events)); $this->assertInstanceOf('\\core\\event\\user_graded', $events[0]); $this->assertEquals($gradeitem->id, $events[0]->other['itemid']); $this->assertInstanceOf('\\core\\event\\user_graded', $events[1]); $this->assertEquals($courseitem->id, $events[1]->other['itemid']); // Remove the overridden flag from the grade, this was set by grade_item::update_final_grade(). $gradegrade = grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $user->id)); $gradegrade->set_overridden(false, false); // Let's change the calculation to anything that won't cause an error. $calculation = calc_formula::unlocalize("=3"); $gradeitem->set_calculation($calculation); // Now force the computation of the grade. $sink = $this->redirectEvents(); grade_regrade_final_grades($course->id); $events = $sink->get_events(); $sink->close(); // Ensure we have two user_graded events, one for the item, one for the course. $this->assertEquals(2, count($events)); $this->assertInstanceOf('\\core\\event\\user_graded', $events[0]); $this->assertEquals($gradeitem->id, $events[0]->other['itemid']); $this->assertInstanceOf('\\core\\event\\user_graded', $events[1]); $this->assertEquals($courseitem->id, $events[1]->other['itemid']); // Now, let's trick the gradebook, we manually update a grade, and flag the grade item as // needing a regrading, so we can trigger the event in grade_item::regrade_final_grades(). $gradeitem = grade_item::fetch($quizitemparams); $gradeitem->set_calculation(''); $gradegrade = grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $user->id)); $gradegrade->rawgrade = 50; $gradegrade->update(); $sink = $this->redirectEvents(); grade_regrade_final_grades($course->id); $events = $sink->get_events(); $sink->close(); // Ensure we have two user_graded events, one for the item, one for the course. $this->assertEquals(2, count($events)); $this->assertInstanceOf('\\core\\event\\user_graded', $events[0]); $this->assertEquals($gradeitem->id, $events[0]->other['itemid']); $this->assertInstanceOf('\\core\\event\\user_graded', $events[1]); $this->assertEquals($courseitem->id, $events[1]->other['itemid']); }
/** * Tests that the event is fired in the correct locations in core. */ public function test_event_is_triggered() { global $DB; // Create the items we need to test with. $course = $this->getDataGenerator()->create_course(); $user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id); $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id)); // Now mark the quiz using grade_update as this is the function that modules use. $grade = array(); $grade['userid'] = $user->id; $grade['rawgrade'] = 50; $sink = $this->redirectEvents(); grade_update('mod/quiz', $course->id, 'mod', 'quiz', $quiz->id, 0, $grade); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Ensure we have a user_graded event. $this->assertEquals(1, count($events)); $this->assertInstanceOf('\core\event\user_graded', $event); // Get the grade item. $gradeitem = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'quiz', 'iteminstance' => $quiz->id, 'courseid' => $course->id)); // Let's alter the grade in the DB so when we call regrade_final_grades() it is changed and an event is called. $sql = "UPDATE {grade_grades} SET finalgrade = '2' WHERE itemid = :itemid AND userid = :userid"; $DB->execute($sql, array('itemid' => $gradeitem->id, 'userid' => $user->id)); // Now check when we regrade this that there is a user graded event. $sink = $this->redirectEvents(); $gradeitem->regrade_final_grades(); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Ensure we have a user_graded event. $this->assertEquals(1, count($events)); $this->assertInstanceOf('\core\event\user_graded', $event); // Remove the grades. $gradeitem->delete_all_grades(); // Now, create a grade using update_raw_grade(). $sink = $this->redirectEvents(); $gradeitem->update_raw_grade($user->id, 50); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Ensure we have a user_graded event. $this->assertEquals(1, count($events)); $this->assertInstanceOf('\core\event\user_graded', $event); // Now, update this grade using update_raw_grade(). $sink = $this->redirectEvents(); $gradeitem->update_raw_grade($user->id, 100); $events = $sink->get_events(); $event = reset($events); $sink->close(); $this->assertEquals(1, count($events)); $this->assertInstanceOf('\core\event\user_graded', $event); // Remove the grades. $gradeitem->delete_all_grades(); // Now, create a grade using update_final_grade(). $sink = $this->redirectEvents(); $gradeitem->update_final_grade($user->id, 50); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Ensure we have a user_graded event. $this->assertEquals(1, count($events)); $this->assertInstanceOf('\core\event\user_graded', $event); // Now, update this grade using update_final_grade(). $sink = $this->redirectEvents(); $gradeitem->update_final_grade($user->id, 100); $events = $sink->get_events(); $event = reset($events); $sink->close(); $this->assertEquals(1, count($events)); $this->assertInstanceOf('\core\event\user_graded', $event); // Let's change the calculation to anything that won't cause an error. $calculation = calc_formula::unlocalize("=3"); $gradeitem->set_calculation($calculation); // Let's alter the grade in the DB so when we call compute() it is changed and an event is called. $sql = "UPDATE {grade_grades} SET finalgrade = 2, overridden = 0 WHERE itemid = :itemid AND userid = :userid"; $DB->execute($sql, array('itemid' => $gradeitem->id, 'userid' => $user->id)); // Now check when we compute that there is a user graded event. $sink = $this->redirectEvents(); $gradeitem->compute(); $events = $sink->get_events(); $event = reset($events); $sink->close(); $this->assertEquals(1, count($events)); $this->assertInstanceOf('\core\event\user_graded', $event); }