/** * Updates all final grades in course. * * @param int $courseid The course ID * @param int $userid If specified try to do a quick regrading of the grades of this user only * @param object $updated_item Optional grade item to be marked for regrading * @param \core\progress\base $progress If provided, will be used to update progress on this long operation. * @return bool true if ok, array of errors if problems found. Grade item id => error message */ function grade_regrade_final_grades($courseid, $userid = null, $updated_item = null, $progress = null) { // This may take a very long time. \core_php_time_limit::raise(); $course_item = grade_item::fetch_course_item($courseid); if ($progress == null) { $progress = new \core\progress\none(); } if ($userid) { // one raw grade updated for one user if (empty($updated_item)) { print_error("cannotbenull", 'debug', '', "updated_item"); } if ($course_item->needsupdate) { $updated_item->force_regrading(); return array($course_item->id => 'Can not do fast regrading after updating of raw grades'); } } else { if (!$course_item->needsupdate) { // nothing to do :-) return true; } } // Categories might have to run some processing before we fetch the grade items. // This gives them a final opportunity to update and mark their children to be updated. // We need to work on the children categories up to the parent ones, so that, for instance, // if a category total is updated it will be reflected in the parent category. $cats = grade_category::fetch_all(array('courseid' => $courseid)); $flatcattree = array(); foreach ($cats as $cat) { if (!isset($flatcattree[$cat->depth])) { $flatcattree[$cat->depth] = array(); } $flatcattree[$cat->depth][] = $cat; } krsort($flatcattree); foreach ($flatcattree as $depth => $cats) { foreach ($cats as $cat) { $cat->pre_regrade_final_grades(); } } $progresstotal = 0; $progresscurrent = 0; $grade_items = grade_item::fetch_all(array('courseid' => $courseid)); $depends_on = array(); foreach ($grade_items as $gid => $gitem) { if ((!empty($updated_item) and $updated_item->id == $gid) || $gitem->is_course_item() || $gitem->is_category_item() || $gitem->is_calculated()) { $grade_items[$gid]->needsupdate = 1; } // We load all dependencies of these items later we can discard some grade_items based on this. if ($grade_items[$gid]->needsupdate) { $depends_on[$gid] = $grade_items[$gid]->depends_on(); $progresstotal++; } } $progress->start_progress('regrade_course', $progresstotal); $errors = array(); $finalids = array(); $updatedids = array(); $gids = array_keys($grade_items); $failed = 0; while (count($finalids) < count($gids)) { // work until all grades are final or error found $count = 0; foreach ($gids as $gid) { if (in_array($gid, $finalids)) { continue; // already final } if (!$grade_items[$gid]->needsupdate) { $finalids[] = $gid; // we can make it final - does not need update continue; } $thisprogress = $progresstotal; foreach ($grade_items as $item) { if ($item->needsupdate) { $thisprogress--; } } // Clip between $progresscurrent and $progresstotal. $thisprogress = max(min($thisprogress, $progresstotal), $progresscurrent); $progress->progress($thisprogress); $progresscurrent = $thisprogress; foreach ($depends_on[$gid] as $did) { if (!in_array($did, $finalids)) { // This item depends on something that is not yet in finals array. continue 2; } } // If this grade item has no dependancy with any updated item at all, then remove it from being recalculated. // When we get here, all of this grade item's decendents are marked as final so they would be marked as updated too // if they would have been regraded. We don't need to regrade items which dependants (not only the direct ones // but any dependant in the cascade) have not been updated. // If $updated_item was specified we discard the grade items that do not depend on it or on any grade item that // depend on $updated_item. // Here we check to see if the direct decendants are marked as updated. if (!empty($updated_item) && $gid != $updated_item->id && !in_array($updated_item->id, $depends_on[$gid])) { // We need to ensure that none of this item's dependencies have been updated. // If we find that one of the direct decendants of this grade item is marked as updated then this // grade item needs to be recalculated and marked as updated. // Being marked as updated is done further down in the code. $updateddependencies = false; foreach ($depends_on[$gid] as $dependency) { if (in_array($dependency, $updatedids)) { $updateddependencies = true; break; } } if ($updateddependencies === false) { // If no direct descendants are marked as updated, then we don't need to update this grade item. We then mark it // as final. $finalids[] = $gid; continue; } } // Let's update, calculate or aggregate. $result = $grade_items[$gid]->regrade_final_grades($userid); if ($result === true) { // We should only update the database if we regraded all users. if (empty($userid)) { $grade_items[$gid]->regrading_finished(); // Do the locktime item locking. $grade_items[$gid]->check_locktime(); } else { $grade_items[$gid]->needsupdate = 0; } $count++; $finalids[] = $gid; $updatedids[] = $gid; } else { $grade_items[$gid]->force_regrading(); $errors[$gid] = $result; } } if ($count == 0) { $failed++; } else { $failed = 0; } if ($failed > 1) { foreach ($gids as $gid) { if (in_array($gid, $finalids)) { continue; // this one is ok } $grade_items[$gid]->force_regrading(); $errors[$grade_items[$gid]->id] = get_string('errorcalculationbroken', 'grades'); } break; // Found error. } } $progress->end_progress(); if (count($errors) == 0) { if (empty($userid)) { // do the locktime locking of grades, but only when doing full regrading grade_grade::check_locktime_all($gids); } return true; } else { return $errors; } }
/** * Updates all final grades in course. * * @param int $courseid * @param int $userid if specified, try to do a quick regrading of grades of this user only * @param object $updated_item the item in which * @return boolean true if ok, array of errors if problems found (item id is used as key) */ function grade_regrade_final_grades($courseid, $userid = null, $updated_item = null) { $course_item = grade_item::fetch_course_item($courseid); if ($userid) { // one raw grade updated for one user if (empty($updated_item)) { error("updated_item_id can not be null!"); } if ($course_item->needsupdate) { $updated_item->force_regrading(); return 'Can not do fast regrading after updating of raw grades'; } } else { if (!$course_item->needsupdate) { // nothing to do :-) return true; } } $grade_items = grade_item::fetch_all(array('courseid' => $courseid)); $depends_on = array(); // first mark all category and calculated items as needing regrading // this is slower, but 100% accurate foreach ($grade_items as $gid => $gitem) { if (!empty($updated_item) and $updated_item->id == $gid) { $grade_items[$gid]->needsupdate = 1; } else { if ($gitem->is_course_item() or $gitem->is_category_item() or $gitem->is_calculated()) { $grade_items[$gid]->needsupdate = 1; } } // construct depends_on lookup array $depends_on[$gid] = $grade_items[$gid]->depends_on(); } $errors = array(); $finalids = array(); $gids = array_keys($grade_items); $failed = 0; while (count($finalids) < count($gids)) { // work until all grades are final or error found $count = 0; foreach ($gids as $gid) { if (in_array($gid, $finalids)) { continue; // already final } if (!$grade_items[$gid]->needsupdate) { $finalids[] = $gid; // we can make it final - does not need update continue; } $doupdate = true; foreach ($depends_on[$gid] as $did) { if (!in_array($did, $finalids)) { $doupdate = false; continue; // this item depends on something that is not yet in finals array } } //oki - let's update, calculate or aggregate :-) if ($doupdate) { $result = $grade_items[$gid]->regrade_final_grades($userid); if ($result === true) { $grade_items[$gid]->regrading_finished(); $grade_items[$gid]->check_locktime(); // do the locktime item locking $count++; $finalids[] = $gid; } else { $grade_items[$gid]->force_regrading(); $errors[$gid] = $result; } } } if ($count == 0) { $failed++; } else { $failed = 0; } if ($failed > 1) { foreach ($gids as $gid) { if (in_array($gid, $finalids)) { continue; // this one is ok } $grade_items[$gid]->force_regrading(); $errors[$grade_items[$gid]->id] = 'Probably circular reference or broken calculation formula'; // TODO: localize } break; // oki, found error } } if (count($errors) == 0) { if (empty($userid)) { // do the locktime locking of grades, but only when doing full regrading grade_grade::check_locktime_all($gids); } return true; } else { return $errors; } }
/** * Updates all final grades in course. * * @param int $courseid The course ID * @param int $userid If specified try to do a quick regrading of the grades of this user only * @param object $updated_item Optional grade item to be marked for regrading * @return bool true if ok, array of errors if problems found. Grade item id => error message */ function grade_regrade_final_grades($courseid, $userid = null, $updated_item = null) { // This may take a very long time. \core_php_time_limit::raise(); $course_item = grade_item::fetch_course_item($courseid); if ($userid) { // one raw grade updated for one user if (empty($updated_item)) { print_error("cannotbenull", 'debug', '', "updated_item"); } if ($course_item->needsupdate) { $updated_item->force_regrading(); return array($course_item->id => 'Can not do fast regrading after updating of raw grades'); } } else { if (!$course_item->needsupdate) { // nothing to do :-) return true; } } // Categories might have to run some processing before we fetch the grade items. // This gives them a final opportunity to update and mark their children to be updated. // We need to work on the children categories up to the parent ones, so that, for instance, // if a category total is updated it will be reflected in the parent category. $cats = grade_category::fetch_all(array('courseid' => $courseid)); $flatcattree = array(); foreach ($cats as $cat) { if (!isset($flatcattree[$cat->depth])) { $flatcattree[$cat->depth] = array(); } $flatcattree[$cat->depth][] = $cat; } krsort($flatcattree); foreach ($flatcattree as $depth => $cats) { foreach ($cats as $cat) { $cat->pre_regrade_final_grades(); } } $grade_items = grade_item::fetch_all(array('courseid' => $courseid)); $depends_on = array(); // first mark all category and calculated items as needing regrading // this is slower, but 100% accurate foreach ($grade_items as $gid => $gitem) { if (!empty($updated_item) and $updated_item->id == $gid) { $grade_items[$gid]->needsupdate = 1; } else { if ($gitem->is_course_item() or $gitem->is_category_item() or $gitem->is_calculated()) { $grade_items[$gid]->needsupdate = 1; } } // construct depends_on lookup array $depends_on[$gid] = $grade_items[$gid]->depends_on(); } $errors = array(); $finalids = array(); $gids = array_keys($grade_items); $failed = 0; while (count($finalids) < count($gids)) { // work until all grades are final or error found $count = 0; foreach ($gids as $gid) { if (in_array($gid, $finalids)) { continue; // already final } if (!$grade_items[$gid]->needsupdate) { $finalids[] = $gid; // we can make it final - does not need update continue; } $doupdate = true; foreach ($depends_on[$gid] as $did) { if (!in_array($did, $finalids)) { $doupdate = false; continue; // this item depends on something that is not yet in finals array } } //oki - let's update, calculate or aggregate :-) if ($doupdate) { $result = $grade_items[$gid]->regrade_final_grades($userid); if ($result === true) { $grade_items[$gid]->regrading_finished(); $grade_items[$gid]->check_locktime(); // do the locktime item locking $count++; $finalids[] = $gid; } else { $grade_items[$gid]->force_regrading(); $errors[$gid] = $result; } } } if ($count == 0) { $failed++; } else { $failed = 0; } if ($failed > 1) { foreach ($gids as $gid) { if (in_array($gid, $finalids)) { continue; // this one is ok } $grade_items[$gid]->force_regrading(); $errors[$grade_items[$gid]->id] = get_string('errorcalculationbroken', 'grades'); } break; // Found error. } } if (count($errors) == 0) { if (empty($userid)) { // do the locktime locking of grades, but only when doing full regrading grade_grade::check_locktime_all($gids); } return true; } else { return $errors; } }