Пример #1
0
 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;
Пример #4
0
 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);
 }
Пример #5
0
 /**
  * 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;
     }
 }
Пример #6
0
 public function test_rand_float()
 {
     $formula = new calc_formula('=rand_float()');
     $result = $formula->evaluate();
     $this->assertTrue(is_float($result));
 }
Пример #7
0
 /**
  * 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');
 }
Пример #8
0
 /**
  *
  */
 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;
 }
Пример #9
0
 /**
  * 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;
 }
Пример #10
0
    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);

    }
Пример #11
0
 /**
  * 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']);
 }
Пример #12
0
    /**
     * 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);
    }