public function test_task_timefilter() { $task = new \local_gradelock\task\lock_grades(); $grade_grade = new grade_grade(); $grade_grade->itemid = $this->grade_items[0]->id; $grade_grade->userid = 10; $grade_grade->rawgrade = 88; $grade_grade->rawgrademax = 110; $grade_grade->rawgrademin = 18; $grade_grade->load_grade_item(); $grade_grade->insert(); $grade_grade->grade_item->update_final_grade($this->user[0]->id, 100, 'gradebook', '', FORMAT_MOODLE); $grade_grade->update(); $task->execute(); $grade_grade = grade_grade::fetch(array('userid' => $this->user[0]->id, 'itemid' => $this->grade_items[0]->id)); $this->assertFalse($grade_grade->is_locked()); }
/** * Tests the gradebookservice get grade service. * * @return void */ public function test_get_grade() { global $DB; $callback = 'block_mhaairs_gradebookservice_external::get_grade'; $this->set_user('admin'); // Add mhaairs grade item directly. $params = array('courseid' => $this->course->id, 'itemtype' => 'manual', 'itemmodule' => 'mhaairs', 'iteminstance' => 101, 'itemname' => 'MH Assignment'); $gitem = new \grade_item($params, false); $gitem->insert('mhaairs'); // Add user grade directly. $params = array('itemid' => $gitem->id, 'userid' => $this->student1->id, 'finalgrade' => '95'); $ggrade = new \grade_grade($params, false); $ggrade->insert('mhaairs'); // Service params. $serviceparams = array('source' => 'mhaairs', 'courseid' => $this->course->id, 'itemtype' => 'manual', 'itemmodule' => 'mhaairs', 'iteminstance' => 101, 'itemnumber' => 0, 'grades' => null, 'itemdetails' => null); // Grade details. $grades = array('userid' => 'student1', 'identity_type' => ''); $gradesjson = urlencode(json_encode($grades)); $serviceparams['grades'] = $gradesjson; $result = call_user_func_array($callback, $serviceparams); $this->assertEquals('MH Assignment', $result['item']['itemname']); $this->assertEquals($this->student1->id, $result['grades'][0]['userid']); $this->assertEquals(95, $result['grades'][0]['grade']); }
/** * Internal function for grade category grade aggregation * * @param int $userid The User ID * @param array $items Grade items * @param array $grade_values Array of grade values * @param object $oldgrade Old grade * @param array $excluded Excluded * @param array $grademinoverrides User specific grademin values if different to the grade_item grademin (key is itemid) * @param array $grademaxoverrides User specific grademax values if different to the grade_item grademax (key is itemid) */ private function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded, $grademinoverrides, $grademaxoverrides) { global $CFG, $DB; // Remember these so we can set flags on them to describe how they were used in the aggregation. $novalue = array(); $dropped = array(); $extracredit = array(); $usedweights = array(); if (empty($userid)) { //ignore first call return; } if ($oldgrade) { $oldfinalgrade = $oldgrade->finalgrade; $grade = new grade_grade($oldgrade, false); $grade->grade_item =& $this->grade_item; } else { // insert final grade - it will be needed later anyway $grade = new grade_grade(array('itemid' => $this->grade_item->id, 'userid' => $userid), false); $grade->grade_item =& $this->grade_item; $grade->insert('system'); $oldfinalgrade = null; } // no need to recalculate locked or overridden grades if ($grade->is_locked() or $grade->is_overridden()) { return; } // can not use own final category grade in calculation unset($grade_values[$this->grade_item->id]); // Make sure a grade_grade exists for every grade_item. // We need to do this so we can set the aggregationstatus // with a set_field call instead of checking if each one exists and creating/updating. if (!empty($items)) { list($ggsql, $params) = $DB->get_in_or_equal(array_keys($items), SQL_PARAMS_NAMED, 'g'); $params['userid'] = $userid; $sql = "SELECT itemid\n FROM {grade_grades}\n WHERE itemid {$ggsql} AND userid = :userid"; $existingitems = $DB->get_records_sql($sql, $params); $notexisting = array_diff(array_keys($items), array_keys($existingitems)); foreach ($notexisting as $itemid) { $gradeitem = $items[$itemid]; $gradegrade = new grade_grade(array('itemid' => $itemid, 'userid' => $userid, 'rawgrademin' => $gradeitem->grademin, 'rawgrademax' => $gradeitem->grademax), false); $gradegrade->grade_item = $gradeitem; $gradegrade->insert('system'); } } // if no grades calculation possible or grading not allowed clear final grade if (empty($grade_values) or empty($items) or $this->grade_item->gradetype != GRADE_TYPE_VALUE and $this->grade_item->gradetype != GRADE_TYPE_SCALE) { $grade->finalgrade = null; if (!is_null($oldfinalgrade)) { $success = $grade->update('aggregation'); // If successful trigger a user_graded event. if ($success) { \core\event\user_graded::create_from_grade($grade)->trigger(); } } $dropped = $grade_values; $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit); return; } // Normalize the grades first - all will have value 0...1 // ungraded items are not used in aggregation. foreach ($grade_values as $itemid => $v) { if (is_null($v)) { // If null, it means no grade. if ($this->aggregateonlygraded) { unset($grade_values[$itemid]); // Mark this item as "excluded empty" because it has no grade. $novalue[$itemid] = 0; continue; } } if (in_array($itemid, $excluded)) { unset($grade_values[$itemid]); $dropped[$itemid] = 0; continue; } // Check for user specific grade min/max overrides. $usergrademin = $items[$itemid]->grademin; $usergrademax = $items[$itemid]->grademax; if (isset($grademinoverrides[$itemid])) { $usergrademin = $grademinoverrides[$itemid]; } if (isset($grademaxoverrides[$itemid])) { $usergrademax = $grademaxoverrides[$itemid]; } if ($this->aggregation == GRADE_AGGREGATE_SUM) { // Assume that the grademin is 0 when standardising the score, to preserve negative grades. $grade_values[$itemid] = grade_grade::standardise_score($v, 0, $usergrademax, 0, 1); } else { $grade_values[$itemid] = grade_grade::standardise_score($v, $usergrademin, $usergrademax, 0, 1); } } // For items with no value, and not excluded - either set their grade to 0 or exclude them. foreach ($items as $itemid => $value) { if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) { if (!$this->aggregateonlygraded) { $grade_values[$itemid] = 0; } else { // We are specifically marking these items as "excluded empty". $novalue[$itemid] = 0; } } } // limit and sort $allvalues = $grade_values; if ($this->can_apply_limit_rules()) { $this->apply_limit_rules($grade_values, $items); } $moredropped = array_diff($allvalues, $grade_values); foreach ($moredropped as $drop => $unused) { $dropped[$drop] = 0; } foreach ($grade_values as $itemid => $val) { if (self::is_extracredit_used() && $items[$itemid]->aggregationcoef > 0) { $extracredit[$itemid] = 0; } } asort($grade_values, SORT_NUMERIC); // let's see we have still enough grades to do any statistics if (count($grade_values) == 0) { // not enough attempts yet $grade->finalgrade = null; if (!is_null($oldfinalgrade)) { $success = $grade->update('aggregation'); // If successful trigger a user_graded event. if ($success) { \core\event\user_graded::create_from_grade($grade)->trigger(); } } $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit); return; } // do the maths $result = $this->aggregate_values_and_adjust_bounds($grade_values, $items, $usedweights, $grademinoverrides, $grademaxoverrides); $agg_grade = $result['grade']; // Set the actual grademin and max to bind the grade properly. $this->grade_item->grademin = $result['grademin']; $this->grade_item->grademax = $result['grademax']; if ($this->aggregation == GRADE_AGGREGATE_SUM) { // The natural aggregation always displays the range as coming from 0 for categories. // However, when we bind the grade we allow for negative values. $result['grademin'] = 0; } // Recalculate the grade back to requested range. $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $result['grademin'], $result['grademax']); $grade->finalgrade = $this->grade_item->bounded_grade($finalgrade); $oldrawgrademin = $grade->rawgrademin; $oldrawgrademax = $grade->rawgrademax; $grade->rawgrademin = $result['grademin']; $grade->rawgrademax = $result['grademax']; // Update in db if changed. if (grade_floats_different($grade->finalgrade, $oldfinalgrade) || grade_floats_different($grade->rawgrademax, $oldrawgrademax) || grade_floats_different($grade->rawgrademin, $oldrawgrademin)) { $success = $grade->update('aggregation'); // If successful trigger a user_graded event. if ($success) { \core\event\user_graded::create_from_grade($grade)->trigger(); } } $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit); return; }
/** * Test sync_completionelements method. * * @dataProvider dataprovider_sync_completionelements * @param array $gradeitem Array of parameters to create grade_item. * @param array $gradegrade Array of parameters to create grade_grade. * @param array $coursecompletion Array of parameters to create coursecompletion. * @param array $studentgrade Array of parameters to create student_grade. * @param int $timenow Current timestamp to enable testing with time. * @param array $expectedstudentgrade Array of parameters we expect to be set in the student_grade. */ public function test_sync_completionelements($gradeitem, $gradegrade, $coursecompletion, $studentgrade, $timenow, $expectedstudentgrade) { global $DB; $sync = new \local_elisprogram\moodle\synchronize(); // Test data setup. $crs = new \course(array('idnumber' => 'CRS1', 'name' => 'Course 1', 'syllabus' => '')); $crs->save(); $cls = new \pmclass(array('courseid' => $crs->id, 'idnumber' => 'CLS1')); $cls->save(); $usr = new \user(array('username' => 'test1', 'idnumber' => 'test2', 'firstname' => 'test', 'lastname' => 'user', 'email' => '*****@*****.**', 'country' => 'CA')); $usr->save(); $musr = $this->getDataGenerator()->create_user(); $gradeitem = new \grade_item($gradeitem, false); $gradeitem->insert(); $gradegrade['itemid'] = $gradeitem->id; $gradegrade['userid'] = $musr->id; $gradegrade = new \grade_grade($gradegrade, false); $gradegrade->insert(); $coursecompletion['courseid'] = $crs->id; $coursecompletion = new \coursecompletion($coursecompletion); $coursecompletion->save(); if ($studentgrade !== false) { $studentgrade['classid'] = $cls->id; $studentgrade['userid'] = $usr->id; $studentgrade['completionid'] = $coursecompletion->id; $studentgrade = new \student_grade($studentgrade); $studentgrade->save(); $studentgrade = new \student_grade($studentgrade->id); $studentgrade->load(); } // Method parameter setup. $causer = (object) array('cmid' => $usr->id, 'pmclassid' => $cls->id); $gis = array($gradeitem->id => (object) array('id' => $gradeitem->id, 'grademax' => $gradeitem->grademax)); $compelements = array($gradeitem->id => (object) array('id' => $coursecompletion->id, 'completion_grade' => $coursecompletion->completion_grade)); $moodlegrades = array($gradeitem->id => $gradegrade); if ($studentgrade !== false) { $cmgrades = array($coursecompletion->id => $studentgrade->to_object()); } else { $cmgrades = array(); } $sync->sync_completionelements($causer, $gis, $compelements, $moodlegrades, $cmgrades, $timenow); $actualstudentgrade = false; if ($studentgrade !== false) { $actualstudentgrade = $DB->get_record(\student_grade::TABLE, array('id' => $studentgrade->id)); } else { $actualstudentgrades = $DB->get_records(\student_grade::TABLE, array(), 'id DESC'); if (!empty($actualstudentgrades)) { $actualstudentgrade = array_shift($actualstudentgrades); } } if ($actualstudentgrade !== false) { if ($expectedstudentgrade !== false) { $expectedstudentgrade['id'] = $actualstudentgrade->id; $expectedstudentgrade['classid'] = $cls->id; $expectedstudentgrade['userid'] = $usr->id; $expectedstudentgrade['completionid'] = $coursecompletion->id; // This is here for tests where we can't reliably predetermine timemodified (i.e. no-sync cases). if (!isset($expectedstudentgrade['timemodified'])) { $expectedstudentgrade['timemodified'] = $actualstudentgrade->timemodified; } $expectedstudentgrade = (object) $expectedstudentgrade; $this->assertEquals($expectedstudentgrade, $actualstudentgrade); } else { $this->assertTrue(false, 'A student_grade was created when one was not expected.'); } } else { // If $expectedstudentgrade is false we were expected no grade to be created. If not, we have a problem. if ($expectedstudentgrade !== false) { $this->assertTrue(false, 'No student_grade created when one was expected'); } else { $this->assertTrue(true); } } }
/** * Validate that the version 1 plugin deletes appropriate associations when * deleting a user */ public function test_version1importdeleteuserdeletesassociations() { global $CFG, $DB; set_config('siteadmins', 0); // New config settings needed for course format refactoring in 2.4. set_config('numsections', 15, 'moodlecourse'); set_config('hiddensections', 0, 'moodlecourse'); set_config('coursedisplay', 1, 'moodlecourse'); require_once $CFG->dirroot . '/cohort/lib.php'; require_once $CFG->dirroot . '/course/lib.php'; require_once $CFG->dirroot . '/group/lib.php'; require_once $CFG->dirroot . '/lib/enrollib.php'; require_once $CFG->dirroot . '/lib/gradelib.php'; // Create our test user, and determine their userid. $this->run_core_user_import(array()); $userid = (int) $DB->get_field('user', 'id', array('username' => 'rlipusername', 'mnethostid' => $CFG->mnet_localhost_id)); // Create cohort. $cohort = new stdClass(); $cohort->name = 'testcohort'; $cohort->contextid = context_system::instance()->id; $cohortid = cohort_add_cohort($cohort); // Add the user to the cohort. cohort_add_member($cohortid, $userid); // Create a course category - there is no API for doing this. $category = new stdClass(); $category->name = 'testcategory'; $category->id = $DB->insert_record('course_categories', $category); // Create a course. set_config('defaultenrol', 1, 'enrol_manual'); set_config('status', ENROL_INSTANCE_ENABLED, 'enrol_manual'); $course = new stdClass(); $course->category = $category->id; $course->fullname = 'testfullname'; $course = create_course($course); // Create a grade. $gradeitem = new grade_item(array('courseid' => $course->id, 'itemtype' => 'manual', 'itemname' => 'testitem'), false); $gradeitem->insert(); $gradegrade = new grade_grade(array('itemid' => $gradeitem->id, 'userid' => $userid), false); $gradegrade->insert(); // Send the user an unprocessed message. set_config('noemailever', true); // Set up a user tag. tag_set('user', $userid, array('testtag')); // Create a new course-level role. $roleid = create_role('testrole', 'testrole', 'testrole'); set_role_contextlevels($roleid, array(CONTEXT_COURSE)); // Enrol the user in the course with the new role. enrol_try_internal_enrol($course->id, $userid, $roleid); // Create a group. $group = new stdClass(); $group->name = 'testgroup'; $group->courseid = $course->id; $groupid = groups_create_group($group); // Add the user to the group. groups_add_member($groupid, $userid); set_user_preference('testname', 'testvalue', $userid); // Create profile field data - don't both with the API here because it's a bit unwieldy. $userinfodata = new stdClass(); $userinfodata->fieldid = 1; $userinfodata->data = 'bogus'; $userinfodata->userid = $userid; $DB->insert_record('user_info_data', $userinfodata); // There is no easily accessible API for doing this. $lastaccess = new stdClass(); $lastaccess->userid = $userid; $lastaccess->courseid = $course->id; $DB->insert_record('user_lastaccess', $lastaccess); $data = array('action' => 'delete', 'username' => 'rlipusername'); $this->run_core_user_import($data, false); // Assert data condition after delete. $this->assertEquals($DB->count_records('message_read', array('useridto' => $userid)), 0); $this->assertEquals($DB->count_records('grade_grades'), 0); $this->assertEquals($DB->count_records('tag_instance'), 0); $this->assertEquals($DB->count_records('cohort_members'), 0); $this->assertEquals($DB->count_records('user_enrolments'), 0); $this->assertEquals($DB->count_records('role_assignments'), 0); $this->assertEquals($DB->count_records('groups_members'), 0); $this->assertEquals($DB->count_records('user_preferences'), 0); $this->assertEquals($DB->count_records('user_info_data'), 0); $this->assertEquals($DB->count_records('user_lastaccess'), 0); }
/** * Internal function for grade category grade aggregation * * @param int $userid The User ID * @param array $items Grade items * @param array $grade_values Array of grade values * @param object $oldgrade Old grade * @param array $excluded Excluded */ private function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded) { global $CFG; if (empty($userid)) { //ignore first call return; } if ($oldgrade) { $oldfinalgrade = $oldgrade->finalgrade; $grade = new grade_grade($oldgrade, false); $grade->grade_item =& $this->grade_item; } else { // insert final grade - it will be needed later anyway $grade = new grade_grade(array('itemid' => $this->grade_item->id, 'userid' => $userid), false); $grade->grade_item =& $this->grade_item; $grade->insert('system'); $oldfinalgrade = null; } // no need to recalculate locked or overridden grades if ($grade->is_locked() or $grade->is_overridden()) { return; } // can not use own final category grade in calculation unset($grade_values[$this->grade_item->id]); // sum is a special aggregation types - it adjusts the min max, does not use relative values if ($this->aggregation == GRADE_AGGREGATE_SUM) { $this->sum_grades($grade, $oldfinalgrade, $items, $grade_values, $excluded); return; } // if no grades calculation possible or grading not allowed clear final grade if (empty($grade_values) or empty($items) or $this->grade_item->gradetype != GRADE_TYPE_VALUE and $this->grade_item->gradetype != GRADE_TYPE_SCALE) { $grade->finalgrade = null; if (!is_null($oldfinalgrade)) { $grade->update('aggregation'); } return; } // normalize the grades first - all will have value 0...1 // ungraded items are not used in aggregation foreach ($grade_values as $itemid => $v) { if (is_null($v)) { // null means no grade unset($grade_values[$itemid]); continue; } else { if (in_array($itemid, $excluded)) { unset($grade_values[$itemid]); continue; } } $grade_values[$itemid] = grade_grade::standardise_score($v, $items[$itemid]->grademin, $items[$itemid]->grademax, 0, 1); } // use min grade if grade missing for these types if (!$this->aggregateonlygraded) { foreach ($items as $itemid => $value) { if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) { $grade_values[$itemid] = 0; } } } // limit and sort $this->apply_limit_rules($grade_values, $items); asort($grade_values, SORT_NUMERIC); // let's see we have still enough grades to do any statistics if (count($grade_values) == 0) { // not enough attempts yet $grade->finalgrade = null; if (!is_null($oldfinalgrade)) { $grade->update('aggregation'); } return; } // do the maths $agg_grade = $this->aggregate_values($grade_values, $items); // recalculate the grade back to requested range $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $this->grade_item->grademin, $this->grade_item->grademax); $grade->finalgrade = $this->grade_item->bounded_grade($finalgrade); // update in db if changed if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) { $grade->update('aggregation'); } return; }
/** * Tests grade_report::blank_hidden_total_and_adjust_bounds() */ public function test_blank_hidden_total_and_adjust_bounds() { global $DB; $this->resetAfterTest(true); $student = $this->getDataGenerator()->create_user(); $this->setUser($student); // Create a course and two activities. // One activity will be hidden. $course = $this->getDataGenerator()->create_course(); $coursegradeitem = grade_item::fetch_course_item($course->id); $coursecontext = context_course::instance($course->id); $data = $this->getDataGenerator()->create_module('data', array('assessed' => 1, 'scale' => 100, 'course' => $course->id)); $datacm = get_coursemodule_from_id('data', $data->cmid); $forum = $this->getDataGenerator()->create_module('forum', array('assessed' => 1, 'scale' => 100, 'course' => $course->id)); $forumcm = get_coursemodule_from_id('forum', $forum->cmid); // Insert student grades for the two activities. $gi = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'data', 'iteminstance' => $data->id, 'courseid' => $course->id)); $datagrade = 50; $grade_grade = new grade_grade(); $grade_grade->itemid = $gi->id; $grade_grade->userid = $student->id; $grade_grade->rawgrade = $datagrade; $grade_grade->finalgrade = $datagrade; $grade_grade->rawgrademax = 100; $grade_grade->rawgrademin = 0; $grade_grade->timecreated = time(); $grade_grade->timemodified = time(); $grade_grade->insert(); $gi = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum->id, 'courseid' => $course->id)); $forumgrade = 70; $grade_grade = new grade_grade(); $grade_grade->itemid = $gi->id; $grade_grade->userid = $student->id; $grade_grade->rawgrade = $forumgrade; $grade_grade->finalgrade = $forumgrade; $grade_grade->rawgrademax = 100; $grade_grade->rawgrademin = 0; $grade_grade->timecreated = time(); $grade_grade->timemodified = time(); $grade_grade->insert(); // Hide the database activity. set_coursemodule_visible($datacm->id, 0); $gpr = new grade_plugin_return(array('type' => 'report', 'courseid' => $course->id)); $report = new grade_report_test($course->id, $gpr, $coursecontext, $student); // Should return the supplied student total grade regardless of hiding. $report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN); $result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade); $this->assertEquals(array('grade' => $datagrade + $forumgrade, 'grademax' => $coursegradeitem->grademax, 'grademin' => $coursegradeitem->grademin, 'aggregationstatus' => 'unknown', 'aggregationweight' => null), $result); // Should blank the student total as course grade depends on a hidden item. $report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN); $result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade); $this->assertEquals(array('grade' => null, 'grademax' => $coursegradeitem->grademax, 'grademin' => $coursegradeitem->grademin, 'aggregationstatus' => 'unknown', 'aggregationweight' => null), $result); // Should return the course total minus the hidden database activity grade. $report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN); $result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade); $this->assertEquals(array('grade' => floatval($forumgrade), 'grademax' => $coursegradeitem->grademax, 'grademin' => $coursegradeitem->grademin, 'aggregationstatus' => 'unknown', 'aggregationweight' => null), $result); // Note: we cannot simply hide modules and call $report->blank_hidden_total() again. // It stores grades in a static variable so $report->blank_hidden_total() will return incorrect totals // In practice this isn't a problem. Grade visibility isn't altered mid-request outside of the unit tests. // Add a second course to test: // 1) How a course with no visible activities behaves. // 2) That $report->blank_hidden_total() correctly moves on to the new course. $course = $this->getDataGenerator()->create_course(); $coursegradeitem = grade_item::fetch_course_item($course->id); $coursecontext = context_course::instance($course->id); $data = $this->getDataGenerator()->create_module('data', array('assessed' => 1, 'scale' => 100, 'course' => $course->id)); $datacm = get_coursemodule_from_id('data', $data->cmid); $forum = $this->getDataGenerator()->create_module('forum', array('assessed' => 1, 'scale' => 100, 'course' => $course->id)); $forumcm = get_coursemodule_from_id('forum', $forum->cmid); $gi = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'data', 'iteminstance' => $data->id, 'courseid' => $course->id)); $datagrade = 50; $grade_grade = new grade_grade(); $grade_grade->itemid = $gi->id; $grade_grade->userid = $student->id; $grade_grade->rawgrade = $datagrade; $grade_grade->finalgrade = $datagrade; $grade_grade->rawgrademax = 100; $grade_grade->rawgrademin = 0; $grade_grade->timecreated = time(); $grade_grade->timemodified = time(); $grade_grade->insert(); $gi = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'forum', 'iteminstance' => $forum->id, 'courseid' => $course->id)); $forumgrade = 70; $grade_grade = new grade_grade(); $grade_grade->itemid = $gi->id; $grade_grade->userid = $student->id; $grade_grade->rawgrade = $forumgrade; $grade_grade->finalgrade = $forumgrade; $grade_grade->rawgrademax = 100; $grade_grade->rawgrademin = 0; $grade_grade->timecreated = time(); $grade_grade->timemodified = time(); $grade_grade->insert(); // Hide both activities. set_coursemodule_visible($datacm->id, 0); set_coursemodule_visible($forumcm->id, 0); $gpr = new grade_plugin_return(array('type' => 'report', 'courseid' => $course->id)); $report = new grade_report_test($course->id, $gpr, $coursecontext, $student); // Should return the supplied student total grade regardless of hiding. $report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN); $result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade); $this->assertEquals(array('grade' => $datagrade + $forumgrade, 'grademax' => $coursegradeitem->grademax, 'grademin' => $coursegradeitem->grademin, 'aggregationstatus' => 'unknown', 'aggregationweight' => null), $result); // Should blank the student total as course grade depends on a hidden item. $report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN); $result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade); $this->assertEquals(array('grade' => null, 'grademax' => $coursegradeitem->grademax, 'grademin' => $coursegradeitem->grademin, 'aggregationstatus' => 'unknown', 'aggregationweight' => null), $result); // Should return the course total minus the hidden activity grades. // They are both hidden so should return null. $report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN); $result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade); $this->assertEquals(array('grade' => null, 'grademax' => $coursegradeitem->grademax, 'grademin' => $coursegradeitem->grademin, 'aggregationstatus' => 'unknown', 'aggregationweight' => null), $result); }
/** * This function creates all the gradebook data from xml */ function restore_create_gradebook($restore, $xml_file) { global $CFG; $status = true; //Check it exists if (!file_exists($xml_file)) { return false; } // Get info from xml // info will contain the number of record to process $info = restore_read_xml_gradebook($restore, $xml_file); // If we have info, then process if (empty($info)) { return $status; } if (empty($CFG->disablegradehistory) and isset($info->gradebook_histories) and $info->gradebook_histories == "true") { $restore_histories = true; } else { $restore_histories = false; } // make sure top course category exists $course_category = grade_category::fetch_course_category($restore->course_id); $course_category->load_grade_item(); // we need to know if all grade items that were backed up are being restored // if that is not the case, we do not restore grade categories nor gradeitems of category type or course type // i.e. the aggregated grades of that category $restoreall = true; // set to false if any grade_item is not selected/restored or already exist $importing = !empty($SESSION->restore->importing); if ($importing) { $restoreall = false; } else { $prev_grade_items = grade_item::fetch_all(array('courseid' => $restore->course_id)); $prev_grade_cats = grade_category::fetch_all(array('courseid' => $restore->course_id)); // if any categories already present, skip restore of categories from backup - course item or category already exist if (count($prev_grade_items) > 1 or count($prev_grade_cats) > 1) { $restoreall = false; } unset($prev_grade_items); unset($prev_grade_cats); if ($restoreall) { if ($recs = get_records_select("backup_ids", "table_name = 'grade_items' AND backup_code = {$restore->backup_unique_code}", "", "old_id")) { foreach ($recs as $rec) { if ($data = backup_getid($restore->backup_unique_code, 'grade_items', $rec->old_id)) { $info = $data->info; // do not restore if this grade_item is a mod, and $itemtype = backup_todb($info['GRADE_ITEM']['#']['ITEMTYPE']['0']['#']); if ($itemtype == 'mod') { $olditeminstance = backup_todb($info['GRADE_ITEM']['#']['ITEMINSTANCE']['0']['#']); $itemmodule = backup_todb($info['GRADE_ITEM']['#']['ITEMMODULE']['0']['#']); if (empty($restore->mods[$itemmodule]->granular)) { continue; } else { if (!empty($restore->mods[$itemmodule]->instances[$olditeminstance]->restore)) { continue; } } // at least one activity should not be restored - do not restore categories and manual items at all $restoreall = false; break; } } } } } } // Start ul if (!defined('RESTORE_SILENTLY')) { echo '<ul>'; } // array of restored categories - speedup ;-) $cached_categories = array(); $outcomes = array(); /// Process letters $context = get_context_instance(CONTEXT_COURSE, $restore->course_id); // respect current grade letters if defined if ($status and $restoreall and !record_exists('grade_letters', 'contextid', $context->id)) { if (!defined('RESTORE_SILENTLY')) { echo '<li>' . get_string('gradeletters', 'grades') . '</li>'; } // Fetch recordset_size records in each iteration $recs = get_records_select("backup_ids", "table_name = 'grade_letters' AND backup_code = {$restore->backup_unique_code}", "", "old_id"); if ($recs) { foreach ($recs as $rec) { // Get the full record from backup_ids $data = backup_getid($restore->backup_unique_code, 'grade_letters', $rec->old_id); if ($data) { $info = $data->info; $dbrec = new object(); $dbrec->contextid = $context->id; $dbrec->lowerboundary = backup_todb($info['GRADE_LETTER']['#']['LOWERBOUNDARY']['0']['#']); $dbrec->letter = backup_todb($info['GRADE_LETTER']['#']['LETTER']['0']['#']); insert_record('grade_letters', $dbrec); } } } } /// Preprocess outcomes - do not store them yet! if ($status and !$importing and $restoreall) { if (!defined('RESTORE_SILENTLY')) { echo '<li>' . get_string('gradeoutcomes', 'grades') . '</li>'; } $recs = get_records_select("backup_ids", "table_name = 'grade_outcomes' AND backup_code = '{$restore->backup_unique_code}'", "", "old_id"); if ($recs) { foreach ($recs as $rec) { //Get the full record from backup_ids $data = backup_getid($restore->backup_unique_code, 'grade_outcomes', $rec->old_id); if ($data) { $info = $data->info; //first find out if outcome already exists $shortname = backup_todb($info['GRADE_OUTCOME']['#']['SHORTNAME']['0']['#']); if ($candidates = get_records_sql("SELECT *\n FROM {$CFG->prefix}grade_outcomes\n WHERE (courseid IS NULL OR courseid = {$restore->course_id})\n AND shortname = '{$shortname}'\n ORDER BY courseid ASC, id ASC")) { $grade_outcome = reset($candidates); $outcomes[$rec->old_id] = $grade_outcome; continue; } $dbrec = new object(); if (has_capability('moodle/grade:manageoutcomes', get_context_instance(CONTEXT_SYSTEM))) { $oldoutcome = backup_todb($info['GRADE_OUTCOME']['#']['COURSEID']['0']['#']); if (empty($oldoutcome)) { //site wide $dbrec->courseid = null; } else { //course only $dbrec->courseid = $restore->course_id; } } else { // no permission to add site outcomes $dbrec->courseid = $restore->course_id; } //Get the fields $dbrec->shortname = backup_todb($info['GRADE_OUTCOME']['#']['SHORTNAME']['0']['#'], false); $dbrec->fullname = backup_todb($info['GRADE_OUTCOME']['#']['FULLNAME']['0']['#'], false); $dbrec->scaleid = backup_todb($info['GRADE_OUTCOME']['#']['SCALEID']['0']['#'], false); $dbrec->description = backup_todb($info['GRADE_OUTCOME']['#']['DESCRIPTION']['0']['#'], false); $dbrec->timecreated = backup_todb($info['GRADE_OUTCOME']['#']['TIMECREATED']['0']['#'], false); $dbrec->timemodified = backup_todb($info['GRADE_OUTCOME']['#']['TIMEMODIFIED']['0']['#'], false); $dbrec->usermodified = backup_todb($info['GRADE_OUTCOME']['#']['USERMODIFIED']['0']['#'], false); //Need to recode the scaleid if ($scale = backup_getid($restore->backup_unique_code, 'scale', $dbrec->scaleid)) { $dbrec->scaleid = $scale->new_id; } //Need to recode the usermodified if ($modifier = backup_getid($restore->backup_unique_code, 'user', $dbrec->usermodified)) { $dbrec->usermodified = $modifier->new_id; } $grade_outcome = new grade_outcome($dbrec, false); $outcomes[$rec->old_id] = $grade_outcome; } } } } /// Process grade items and grades if ($status) { if (!defined('RESTORE_SILENTLY')) { echo '<li>' . get_string('gradeitems', 'grades') . '</li>'; } $counter = 0; //Fetch recordset_size records in each iteration $recs = get_records_select("backup_ids", "table_name = 'grade_items' AND backup_code = '{$restore->backup_unique_code}'", "id", "old_id"); if ($recs) { foreach ($recs as $rec) { //Get the full record from backup_ids $data = backup_getid($restore->backup_unique_code, 'grade_items', $rec->old_id); if ($data) { $info = $data->info; // first find out if category or normal item $itemtype = backup_todb($info['GRADE_ITEM']['#']['ITEMTYPE']['0']['#'], false); if ($itemtype == 'course' or $itemtype == 'category') { if (!$restoreall or $importing) { continue; } $oldcat = backup_todb($info['GRADE_ITEM']['#']['ITEMINSTANCE']['0']['#'], false); if (!($cdata = backup_getid($restore->backup_unique_code, 'grade_categories', $oldcat))) { continue; } $cinfo = $cdata->info; unset($cdata); if ($itemtype == 'course') { $course_category->fullname = backup_todb($cinfo['GRADE_CATEGORY']['#']['FULLNAME']['0']['#'], false); $course_category->aggregation = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATION']['0']['#'], false); $course_category->keephigh = backup_todb($cinfo['GRADE_CATEGORY']['#']['KEEPHIGH']['0']['#'], false); $course_category->droplow = backup_todb($cinfo['GRADE_CATEGORY']['#']['DROPLOW']['0']['#'], false); $course_category->aggregateonlygraded = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATEONLYGRADED']['0']['#'], false); $course_category->aggregateoutcomes = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATEOUTCOMES']['0']['#'], false); $course_category->aggregatesubcats = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATESUBCATS']['0']['#'], false); $course_category->timecreated = backup_todb($cinfo['GRADE_CATEGORY']['#']['TIMECREATED']['0']['#'], false); $course_category->update('restore'); $status = backup_putid($restore->backup_unique_code, 'grade_categories', $oldcat, $course_category->id) && $status; $cached_categories[$oldcat] = $course_category; $grade_item = $course_category->get_grade_item(); } else { $oldparent = backup_todb($cinfo['GRADE_CATEGORY']['#']['PARENT']['0']['#'], false); if (empty($cached_categories[$oldparent])) { debugging('parent not found ' . $oldparent); continue; // parent not found, sorry } $grade_category = new grade_category(); $grade_category->courseid = $restore->course_id; $grade_category->parent = $cached_categories[$oldparent]->id; $grade_category->fullname = backup_todb($cinfo['GRADE_CATEGORY']['#']['FULLNAME']['0']['#'], false); $grade_category->aggregation = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATION']['0']['#'], false); $grade_category->keephigh = backup_todb($cinfo['GRADE_CATEGORY']['#']['KEEPHIGH']['0']['#'], false); $grade_category->droplow = backup_todb($cinfo['GRADE_CATEGORY']['#']['DROPLOW']['0']['#'], false); $grade_category->aggregateonlygraded = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATEONLYGRADED']['0']['#'], false); $grade_category->aggregateoutcomes = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATEOUTCOMES']['0']['#'], false); $grade_category->aggregatesubcats = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATESUBCATS']['0']['#'], false); $grade_category->timecreated = backup_todb($cinfo['GRADE_CATEGORY']['#']['TIMECREATED']['0']['#'], false); $grade_category->insert('restore'); $status = backup_putid($restore->backup_unique_code, 'grade_categories', $oldcat, $grade_category->id) && $status; $cached_categories[$oldcat] = $grade_category; $grade_item = $grade_category->get_grade_item(); // creates grade_item too } unset($cinfo); $idnumber = backup_todb($info['GRADE_ITEM']['#']['IDNUMBER']['0']['#'], false); if (grade_verify_idnumber($idnumber, $restore->course_id)) { $grade_item->idnumber = $idnumber; } $grade_item->itemname = backup_todb($info['GRADE_ITEM']['#']['ITEMNAME']['0']['#'], false); $grade_item->iteminfo = backup_todb($info['GRADE_ITEM']['#']['ITEMINFO']['0']['#'], false); $grade_item->gradetype = backup_todb($info['GRADE_ITEM']['#']['GRADETYPE']['0']['#'], false); $grade_item->calculation = backup_todb($info['GRADE_ITEM']['#']['CALCULATION']['0']['#'], false); $grade_item->grademax = backup_todb($info['GRADE_ITEM']['#']['GRADEMAX']['0']['#'], false); $grade_item->grademin = backup_todb($info['GRADE_ITEM']['#']['GRADEMIN']['0']['#'], false); $grade_item->gradepass = backup_todb($info['GRADE_ITEM']['#']['GRADEPASS']['0']['#'], false); $grade_item->multfactor = backup_todb($info['GRADE_ITEM']['#']['MULTFACTOR']['0']['#'], false); $grade_item->plusfactor = backup_todb($info['GRADE_ITEM']['#']['PLUSFACTOR']['0']['#'], false); $grade_item->aggregationcoef = backup_todb($info['GRADE_ITEM']['#']['AGGREGATIONCOEF']['0']['#'], false); $grade_item->display = backup_todb($info['GRADE_ITEM']['#']['DISPLAY']['0']['#'], false); $grade_item->decimals = backup_todb($info['GRADE_ITEM']['#']['DECIMALS']['0']['#'], false); $grade_item->hidden = backup_todb($info['GRADE_ITEM']['#']['HIDDEN']['0']['#'], false); $grade_item->locked = backup_todb($info['GRADE_ITEM']['#']['LOCKED']['0']['#'], false); $grade_item->locktime = backup_todb($info['GRADE_ITEM']['#']['LOCKTIME']['0']['#'], false); $grade_item->timecreated = backup_todb($info['GRADE_ITEM']['#']['TIMECREATED']['0']['#'], false); if (backup_todb($info['GRADE_ITEM']['#']['SCALEID']['0']['#'], false)) { $scale = backup_getid($restore->backup_unique_code, "scale", backup_todb($info['GRADE_ITEM']['#']['SCALEID']['0']['#'], false)); $grade_item->scaleid = $scale->new_id; } if (backup_todb($info['GRADE_ITEM']['#']['OUTCOMEID']['0']['#'], false)) { $outcome = backup_getid($restore->backup_unique_code, "grade_outcomes", backup_todb($info['GRADE_ITEM']['#']['OUTCOMEID']['0']['#'], false)); $grade_item->outcomeid = $outcome->new_id; } $grade_item->update('restore'); $status = backup_putid($restore->backup_unique_code, "grade_items", $rec->old_id, $grade_item->id) && $status; } else { if ($itemtype != 'mod' and (!$restoreall or $importing)) { // not extra gradebook stuff if restoring individual activities or something already there continue; } $dbrec = new object(); $dbrec->courseid = $restore->course_id; $dbrec->itemtype = backup_todb($info['GRADE_ITEM']['#']['ITEMTYPE']['0']['#'], false); $dbrec->itemmodule = backup_todb($info['GRADE_ITEM']['#']['ITEMMODULE']['0']['#'], false); if ($itemtype == 'mod') { // iteminstance should point to new mod $olditeminstance = backup_todb($info['GRADE_ITEM']['#']['ITEMINSTANCE']['0']['#'], false); $mod = backup_getid($restore->backup_unique_code, $dbrec->itemmodule, $olditeminstance); $dbrec->iteminstance = $mod->new_id; if (!($cm = get_coursemodule_from_instance($dbrec->itemmodule, $mod->new_id))) { // item not restored - no item continue; } // keep in sync with activity idnumber $dbrec->idnumber = $cm->idnumber; } else { $idnumber = backup_todb($info['GRADE_ITEM']['#']['IDNUMBER']['0']['#'], false); if (grade_verify_idnumber($idnumber, $restore->course_id)) { //make sure the new idnumber is unique $dbrec->idnumber = $idnumber; } } $dbrec->itemname = backup_todb($info['GRADE_ITEM']['#']['ITEMNAME']['0']['#'], false); $dbrec->itemtype = backup_todb($info['GRADE_ITEM']['#']['ITEMTYPE']['0']['#'], false); $dbrec->itemmodule = backup_todb($info['GRADE_ITEM']['#']['ITEMMODULE']['0']['#'], false); $dbrec->itemnumber = backup_todb($info['GRADE_ITEM']['#']['ITEMNUMBER']['0']['#'], false); $dbrec->iteminfo = backup_todb($info['GRADE_ITEM']['#']['ITEMINFO']['0']['#'], false); $dbrec->gradetype = backup_todb($info['GRADE_ITEM']['#']['GRADETYPE']['0']['#'], false); $dbrec->calculation = backup_todb($info['GRADE_ITEM']['#']['CALCULATION']['0']['#'], false); $dbrec->grademax = backup_todb($info['GRADE_ITEM']['#']['GRADEMAX']['0']['#'], false); $dbrec->grademin = backup_todb($info['GRADE_ITEM']['#']['GRADEMIN']['0']['#'], false); $dbrec->gradepass = backup_todb($info['GRADE_ITEM']['#']['GRADEPASS']['0']['#'], false); $dbrec->multfactor = backup_todb($info['GRADE_ITEM']['#']['MULTFACTOR']['0']['#'], false); $dbrec->plusfactor = backup_todb($info['GRADE_ITEM']['#']['PLUSFACTOR']['0']['#'], false); $dbrec->aggregationcoef = backup_todb($info['GRADE_ITEM']['#']['AGGREGATIONCOEF']['0']['#'], false); $dbrec->display = backup_todb($info['GRADE_ITEM']['#']['DISPLAY']['0']['#'], false); $dbrec->decimals = backup_todb($info['GRADE_ITEM']['#']['DECIMALS']['0']['#'], false); $dbrec->hidden = backup_todb($info['GRADE_ITEM']['#']['HIDDEN']['0']['#'], false); $dbrec->locked = backup_todb($info['GRADE_ITEM']['#']['LOCKED']['0']['#'], false); $dbrec->locktime = backup_todb($info['GRADE_ITEM']['#']['LOCKTIME']['0']['#'], false); $dbrec->timecreated = backup_todb($info['GRADE_ITEM']['#']['TIMECREATED']['0']['#'], false); if (backup_todb($info['GRADE_ITEM']['#']['SCALEID']['0']['#'], false)) { $scale = backup_getid($restore->backup_unique_code, "scale", backup_todb($info['GRADE_ITEM']['#']['SCALEID']['0']['#'], false)); $dbrec->scaleid = $scale->new_id; } if (backup_todb($info['GRADE_ITEM']['#']['OUTCOMEID']['0']['#'])) { $oldoutcome = backup_todb($info['GRADE_ITEM']['#']['OUTCOMEID']['0']['#']); if (empty($outcomes[$oldoutcome])) { continue; // error! } if (empty($outcomes[$oldoutcome]->id)) { $outcomes[$oldoutcome]->insert('restore'); $outcomes[$oldoutcome]->use_in($restore->course_id); backup_putid($restore->backup_unique_code, "grade_outcomes", $oldoutcome, $outcomes[$oldoutcome]->id); } $dbrec->outcomeid = $outcomes[$oldoutcome]->id; } $grade_item = new grade_item($dbrec, false); $grade_item->insert('restore'); if ($restoreall) { // set original parent if restored $oldcat = $info['GRADE_ITEM']['#']['CATEGORYID']['0']['#']; if (!empty($cached_categories[$oldcat])) { $grade_item->set_parent($cached_categories[$oldcat]->id); } } $status = backup_putid($restore->backup_unique_code, "grade_items", $rec->old_id, $grade_item->id) && $status; } // no need to restore grades if user data is not selected or importing activities if ($importing or $grade_item->itemtype == 'mod' and !restore_userdata_selected($restore, $grade_item->itemmodule, $olditeminstance)) { // module instance not selected when restored using granular // skip this item continue; } /// now, restore grade_grades if (!empty($info['GRADE_ITEM']['#']['GRADE_GRADES']['0']['#']['GRADE'])) { //Iterate over items foreach ($info['GRADE_ITEM']['#']['GRADE_GRADES']['0']['#']['GRADE'] as $g_info) { $grade = new grade_grade(); $grade->itemid = $grade_item->id; $olduser = backup_todb($g_info['#']['USERID']['0']['#'], false); $user = backup_getid($restore->backup_unique_code, "user", $olduser); $grade->userid = $user->new_id; $grade->rawgrade = backup_todb($g_info['#']['RAWGRADE']['0']['#'], false); $grade->rawgrademax = backup_todb($g_info['#']['RAWGRADEMAX']['0']['#'], false); $grade->rawgrademin = backup_todb($g_info['#']['RAWGRADEMIN']['0']['#'], false); // need to find scaleid if (backup_todb($g_info['#']['RAWSCALEID']['0']['#'])) { $scale = backup_getid($restore->backup_unique_code, "scale", backup_todb($g_info['#']['RAWSCALEID']['0']['#'], false)); $grade->rawscaleid = $scale->new_id; } if (backup_todb($g_info['#']['USERMODIFIED']['0']['#'])) { if ($modifier = backup_getid($restore->backup_unique_code, "user", backup_todb($g_info['#']['USERMODIFIED']['0']['#'], false))) { $grade->usermodified = $modifier->new_id; } } $grade->finalgrade = backup_todb($g_info['#']['FINALGRADE']['0']['#'], false); $grade->hidden = backup_todb($g_info['#']['HIDDEN']['0']['#'], false); $grade->locked = backup_todb($g_info['#']['LOCKED']['0']['#'], false); $grade->locktime = backup_todb($g_info['#']['LOCKTIME']['0']['#'], false); $grade->exported = backup_todb($g_info['#']['EXPORTED']['0']['#'], false); $grade->overridden = backup_todb($g_info['#']['OVERRIDDEN']['0']['#'], false); $grade->excluded = backup_todb($g_info['#']['EXCLUDED']['0']['#'], false); $grade->feedback = backup_todb($g_info['#']['FEEDBACK']['0']['#'], false); $grade->feedbackformat = backup_todb($g_info['#']['FEEDBACKFORMAT']['0']['#'], false); $grade->information = backup_todb($g_info['#']['INFORMATION']['0']['#'], false); $grade->informationformat = backup_todb($g_info['#']['INFORMATIONFORMAT']['0']['#'], false); $grade->timecreated = backup_todb($g_info['#']['TIMECREATED']['0']['#'], false); $grade->timemodified = backup_todb($g_info['#']['TIMEMODIFIED']['0']['#'], false); $grade->insert('restore'); backup_putid($restore->backup_unique_code, "grade_grades", backup_todb($g_info['#']['ID']['0']['#']), $grade->id); $counter++; if ($counter % 20 == 0) { if (!defined('RESTORE_SILENTLY')) { echo "."; if ($counter % 400 == 0) { echo "<br />"; } } backup_flush(300); } } } } } } } /// add outcomes that are not used when doing full restore if ($status and $restoreall) { foreach ($outcomes as $oldoutcome => $grade_outcome) { if (empty($grade_outcome->id)) { $grade_outcome->insert('restore'); $grade_outcome->use_in($restore->course_id); backup_putid($restore->backup_unique_code, "grade_outcomes", $oldoutcome, $grade_outcome->id); } } } if ($status and !$importing and $restore_histories) { /// following code is very inefficient $gchcount = count_records('backup_ids', 'backup_code', $restore->backup_unique_code, 'table_name', 'grade_categories_history'); $gghcount = count_records('backup_ids', 'backup_code', $restore->backup_unique_code, 'table_name', 'grade_grades_history'); $gihcount = count_records('backup_ids', 'backup_code', $restore->backup_unique_code, 'table_name', 'grade_items_history'); $gohcount = count_records('backup_ids', 'backup_code', $restore->backup_unique_code, 'table_name', 'grade_outcomes_history'); // Number of records to get in every chunk $recordset_size = 2; // process histories if ($gchcount && $status) { if (!defined('RESTORE_SILENTLY')) { echo '<li>' . get_string('gradecategoryhistory', 'grades') . '</li>'; } $counter = 0; while ($counter < $gchcount) { //Fetch recordset_size records in each iteration $recs = get_records_select("backup_ids", "table_name = 'grade_categories_history' AND backup_code = '{$restore->backup_unique_code}'", "old_id", "old_id", $counter, $recordset_size); if ($recs) { foreach ($recs as $rec) { //Get the full record from backup_ids $data = backup_getid($restore->backup_unique_code, 'grade_categories_history', $rec->old_id); if ($data) { //Now get completed xmlized object $info = $data->info; //traverse_xmlize($info); //Debug //print_object ($GLOBALS['traverse_array']); //Debug //$GLOBALS['traverse_array']=""; //Debug $oldobj = backup_getid($restore->backup_unique_code, "grade_categories", backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['OLDID']['0']['#'])); if (empty($oldobj->new_id)) { // if the old object is not being restored, can't restoring its history $counter++; continue; } $dbrec->oldid = $oldobj->new_id; $dbrec->action = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['ACTION']['0']['#']); $dbrec->source = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['SOURCE']['0']['#']); $dbrec->timemodified = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['TIMEMODIFIED']['0']['#']); // loggeduser might not be restored, e.g. admin if ($oldobj = backup_getid($restore->backup_unique_code, "user", backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['LOGGEDUSER']['0']['#']))) { $dbrec->loggeduser = $oldobj->new_id; } // this item might not have a parent at all, do not skip it if no parent is specified if (backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['PARENT']['0']['#'])) { $oldobj = backup_getid($restore->backup_unique_code, "grade_categories", backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['PARENT']['0']['#'])); if (empty($oldobj->new_id)) { // if the parent category not restored $counter++; continue; } } $dbrec->parent = $oldobj->new_id; $dbrec->depth = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['DEPTH']['0']['#']); // path needs to be rebuilt if ($path = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['PATH']['0']['#'])) { // to preserve the path and make it work, we need to replace the categories one by one // we first get the list of categories in current path if ($paths = explode("/", $path)) { $newpath = ''; foreach ($paths as $catid) { if ($catid) { // find the new corresponding path $oldpath = backup_getid($restore->backup_unique_code, "grade_categories", $catid); $newpath .= "/{$oldpath->new_id}"; } } $dbrec->path = $newpath; } } $dbrec->fullname = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['FULLNAME']['0']['#']); $dbrec->aggregation = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['AGGRETGATION']['0']['#']); $dbrec->keephigh = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['KEEPHIGH']['0']['#']); $dbrec->droplow = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['DROPLOW']['0']['#']); $dbrec->aggregateonlygraded = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['AGGREGATEONLYGRADED']['0']['#']); $dbrec->aggregateoutcomes = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['AGGREGATEOUTCOMES']['0']['#']); $dbrec->aggregatesubcats = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['AGGREGATESUBCATS']['0']['#']); $dbrec->courseid = $restore->course_id; insert_record('grade_categories_history', $dbrec); unset($dbrec); } //Increment counters $counter++; //Do some output if ($counter % 1 == 0) { if (!defined('RESTORE_SILENTLY')) { echo "."; if ($counter % 20 == 0) { echo "<br />"; } } backup_flush(300); } } } } } // process histories if ($gghcount && $status) { if (!defined('RESTORE_SILENTLY')) { echo '<li>' . get_string('gradegradeshistory', 'grades') . '</li>'; } $counter = 0; while ($counter < $gghcount) { //Fetch recordset_size records in each iteration $recs = get_records_select("backup_ids", "table_name = 'grade_grades_history' AND backup_code = '{$restore->backup_unique_code}'", "old_id", "old_id", $counter, $recordset_size); if ($recs) { foreach ($recs as $rec) { //Get the full record from backup_ids $data = backup_getid($restore->backup_unique_code, 'grade_grades_history', $rec->old_id); if ($data) { //Now get completed xmlized object $info = $data->info; //traverse_xmlize($info); //Debug //print_object ($GLOBALS['traverse_array']); //Debug //$GLOBALS['traverse_array']=""; //Debug $oldobj = backup_getid($restore->backup_unique_code, "grade_grades", backup_todb($info['GRADE_GRADES_HISTORY']['#']['OLDID']['0']['#'])); if (empty($oldobj->new_id)) { // if the old object is not being restored, can't restoring its history $counter++; continue; } $dbrec->oldid = $oldobj->new_id; $dbrec->action = backup_todb($info['GRADE_GRADES_HISTORY']['#']['ACTION']['0']['#']); $dbrec->source = backup_todb($info['GRADE_GRADES_HISTORY']['#']['SOURCE']['0']['#']); $dbrec->timemodified = backup_todb($info['GRADE_GRADES_HISTORY']['#']['TIMEMODIFIED']['0']['#']); if ($oldobj = backup_getid($restore->backup_unique_code, "user", backup_todb($info['GRADE_GRADES_HISTORY']['#']['LOGGEDUSER']['0']['#']))) { $dbrec->loggeduser = $oldobj->new_id; } $oldobj = backup_getid($restore->backup_unique_code, "grade_items", backup_todb($info['GRADE_GRADES_HISTORY']['#']['ITEMID']['0']['#'])); $dbrec->itemid = $oldobj->new_id; if (empty($dbrec->itemid)) { $counter++; continue; // grade item not being restored } $oldobj = backup_getid($restore->backup_unique_code, "user", backup_todb($info['GRADE_GRADES_HISTORY']['#']['USERID']['0']['#'])); $dbrec->userid = $oldobj->new_id; $dbrec->rawgrade = backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWGRADE']['0']['#']); $dbrec->rawgrademax = backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWGRADEMAX']['0']['#']); $dbrec->rawgrademin = backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWGRADEMIN']['0']['#']); if ($oldobj = backup_getid($restore->backup_unique_code, "user", backup_todb($info['GRADE_GRADES_HISTORY']['#']['USERMODIFIED']['0']['#']))) { $dbrec->usermodified = $oldobj->new_id; } if (backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWSCALEID']['0']['#'])) { $scale = backup_getid($restore->backup_unique_code, "scale", backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWSCALEID']['0']['#'])); $dbrec->rawscaleid = $scale->new_id; } $dbrec->finalgrade = backup_todb($info['GRADE_GRADES_HISTORY']['#']['FINALGRADE']['0']['#']); $dbrec->hidden = backup_todb($info['GRADE_GRADES_HISTORY']['#']['HIDDEN']['0']['#']); $dbrec->locked = backup_todb($info['GRADE_GRADES_HISTORY']['#']['LOCKED']['0']['#']); $dbrec->locktime = backup_todb($info['GRADE_GRADES_HISTORY']['#']['LOCKTIME']['0']['#']); $dbrec->exported = backup_todb($info['GRADE_GRADES_HISTORY']['#']['EXPORTED']['0']['#']); $dbrec->overridden = backup_todb($info['GRADE_GRADES_HISTORY']['#']['OVERRIDDEN']['0']['#']); $dbrec->excluded = backup_todb($info['GRADE_GRADES_HISTORY']['#']['EXCLUDED']['0']['#']); $dbrec->feedback = backup_todb($info['GRADE_TEXT_HISTORY']['#']['FEEDBACK']['0']['#']); $dbrec->feedbackformat = backup_todb($info['GRADE_TEXT_HISTORY']['#']['FEEDBACKFORMAT']['0']['#']); $dbrec->information = backup_todb($info['GRADE_TEXT_HISTORY']['#']['INFORMATION']['0']['#']); $dbrec->informationformat = backup_todb($info['GRADE_TEXT_HISTORY']['#']['INFORMATIONFORMAT']['0']['#']); insert_record('grade_grades_history', $dbrec); unset($dbrec); } //Increment counters $counter++; //Do some output if ($counter % 1 == 0) { if (!defined('RESTORE_SILENTLY')) { echo "."; if ($counter % 20 == 0) { echo "<br />"; } } backup_flush(300); } } } } } // process histories if ($gihcount && $status) { if (!defined('RESTORE_SILENTLY')) { echo '<li>' . get_string('gradeitemshistory', 'grades') . '</li>'; } $counter = 0; while ($counter < $gihcount) { //Fetch recordset_size records in each iteration $recs = get_records_select("backup_ids", "table_name = 'grade_items_history' AND backup_code = '{$restore->backup_unique_code}'", "old_id", "old_id", $counter, $recordset_size); if ($recs) { foreach ($recs as $rec) { //Get the full record from backup_ids $data = backup_getid($restore->backup_unique_code, 'grade_items_history', $rec->old_id); if ($data) { //Now get completed xmlized object $info = $data->info; //traverse_xmlize($info); //Debug //print_object ($GLOBALS['traverse_array']); //Debug //$GLOBALS['traverse_array']=""; //Debug $oldobj = backup_getid($restore->backup_unique_code, "grade_items", backup_todb($info['GRADE_ITEM_HISTORY']['#']['OLDID']['0']['#'])); if (empty($oldobj->new_id)) { // if the old object is not being restored, can't restoring its history $counter++; continue; } $dbrec->oldid = $oldobj->new_id; $dbrec->action = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ACTION']['0']['#']); $dbrec->source = backup_todb($info['GRADE_ITEM_HISTORY']['#']['SOURCE']['0']['#']); $dbrec->timemodified = backup_todb($info['GRADE_ITEM_HISTORY']['#']['TIMEMODIFIED']['0']['#']); if ($oldobj = backup_getid($restore->backup_unique_code, "user", backup_todb($info['GRADE_ITEM_HISTORY']['#']['LOGGEDUSER']['0']['#']))) { $dbrec->loggeduser = $oldobj->new_id; } $dbrec->courseid = $restore->course_id; $oldobj = backup_getid($restore->backup_unique_code, 'grade_categories', backup_todb($info['GRADE_ITEM_HISTORY']['#']['CATEGORYID']['0']['#'])); $oldobj->categoryid = $category->new_id; if (empty($oldobj->categoryid)) { $counter++; continue; // category not restored } $dbrec->itemname = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMNAME']['0']['#']); $dbrec->itemtype = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMTYPE']['0']['#']); $dbrec->itemmodule = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMMODULE']['0']['#']); // code from grade_items restore $iteminstance = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMINSTANCE']['0']['#']); // do not restore if this grade_item is a mod, and if ($dbrec->itemtype == 'mod') { if (!restore_userdata_selected($restore, $dbrec->itemmodule, $iteminstance)) { // module instance not selected when restored using granular // skip this item $counter++; continue; } // iteminstance should point to new mod $mod = backup_getid($restore->backup_unique_code, $dbrec->itemmodule, $iteminstance); $dbrec->iteminstance = $mod->new_id; } else { if ($dbrec->itemtype == 'category') { // the item instance should point to the new grade category // only proceed if we are restoring all grade items if ($restoreall) { $category = backup_getid($restore->backup_unique_code, 'grade_categories', $iteminstance); $dbrec->iteminstance = $category->new_id; } else { // otherwise we can safely ignore this grade item and subsequent // grade_raws, grade_finals etc continue; } } elseif ($dbrec->itemtype == 'course') { // We don't restore course type to avoid duplicate course items if ($restoreall) { // TODO any special code needed here to restore course item without duplicating it? // find the course category with depth 1, and course id = current course id // this would have been already restored $cat = get_record('grade_categories', 'depth', 1, 'courseid', $restore->course_id); $dbrec->iteminstance = $cat->id; } else { $counter++; continue; } } } $dbrec->itemnumber = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMNUMBER']['0']['#']); $dbrec->iteminfo = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMINFO']['0']['#']); $dbrec->idnumber = backup_todb($info['GRADE_ITEM_HISTORY']['#']['IDNUMBER']['0']['#']); $dbrec->calculation = backup_todb($info['GRADE_ITEM_HISTORY']['#']['CALCULATION']['0']['#']); $dbrec->gradetype = backup_todb($info['GRADE_ITEM_HISTORY']['#']['GRADETYPE']['0']['#']); $dbrec->grademax = backup_todb($info['GRADE_ITEM_HISTORY']['#']['GRADEMAX']['0']['#']); $dbrec->grademin = backup_todb($info['GRADE_ITEM_HISTORY']['#']['GRADEMIN']['0']['#']); if ($oldobj = backup_getid($restore->backup_unique_code, "scale", backup_todb($info['GRADE_ITEM_HISTORY']['#']['SCALEID']['0']['#']))) { // scaleid is optional $dbrec->scaleid = $oldobj->new_id; } if ($oldobj = backup_getid($restore->backup_unique_code, "grade_outcomes", backup_todb($info['GRADE_ITEM_HISTORY']['#']['OUTCOMEID']['0']['#']))) { // outcome is optional $dbrec->outcomeid = $oldobj->new_id; } $dbrec->gradepass = backup_todb($info['GRADE_ITEM_HISTORY']['#']['GRADEPASS']['0']['#']); $dbrec->multfactor = backup_todb($info['GRADE_ITEM_HISTORY']['#']['MULTFACTOR']['0']['#']); $dbrec->plusfactor = backup_todb($info['GRADE_ITEM_HISTORY']['#']['PLUSFACTOR']['0']['#']); $dbrec->aggregationcoef = backup_todb($info['GRADE_ITEM_HISTORY']['#']['AGGREGATIONCOEF']['0']['#']); $dbrec->sortorder = backup_todb($info['GRADE_ITEM_HISTORY']['#']['SORTORDER']['0']['#']); $dbrec->display = backup_todb($info['GRADE_ITEM_HISTORY']['#']['DISPLAY']['0']['#']); $dbrec->decimals = backup_todb($info['GRADE_ITEM_HISTORY']['#']['DECIMALS']['0']['#']); $dbrec->hidden = backup_todb($info['GRADE_ITEM_HISTORY']['#']['HIDDEN']['0']['#']); $dbrec->locked = backup_todb($info['GRADE_ITEM_HISTORY']['#']['LOCKED']['0']['#']); $dbrec->locktime = backup_todb($info['GRADE_ITEM_HISTORY']['#']['LOCKTIME']['0']['#']); $dbrec->needsupdate = backup_todb($info['GRADE_ITEM_HISTORY']['#']['NEEDSUPDATE']['0']['#']); insert_record('grade_items_history', $dbrec); unset($dbrec); } //Increment counters $counter++; //Do some output if ($counter % 1 == 0) { if (!defined('RESTORE_SILENTLY')) { echo "."; if ($counter % 20 == 0) { echo "<br />"; } } backup_flush(300); } } } } } // process histories if ($gohcount && $status) { if (!defined('RESTORE_SILENTLY')) { echo '<li>' . get_string('gradeoutcomeshistory', 'grades') . '</li>'; } $counter = 0; while ($counter < $gohcount) { //Fetch recordset_size records in each iteration $recs = get_records_select("backup_ids", "table_name = 'grade_outcomes_history' AND backup_code = '{$restore->backup_unique_code}'", "old_id", "old_id", $counter, $recordset_size); if ($recs) { foreach ($recs as $rec) { //Get the full record from backup_ids $data = backup_getid($restore->backup_unique_code, 'grade_outcomes_history', $rec->old_id); if ($data) { //Now get completed xmlized object $info = $data->info; //traverse_xmlize($info); //Debug //print_object ($GLOBALS['traverse_array']); //Debug //$GLOBALS['traverse_array']=""; //Debug $oldobj = backup_getid($restore->backup_unique_code, "grade_outcomes", backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['OLDID']['0']['#'])); if (empty($oldobj->new_id)) { // if the old object is not being restored, can't restoring its history $counter++; continue; } $dbrec->oldid = $oldobj->new_id; $dbrec->action = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['ACTION']['0']['#']); $dbrec->source = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['SOURCE']['0']['#']); $dbrec->timemodified = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['TIMEMODIFIED']['0']['#']); if ($oldobj = backup_getid($restore->backup_unique_code, "user", backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['LOGGEDUSER']['0']['#']))) { $dbrec->loggeduser = $oldobj->new_id; } $dbrec->courseid = $restore->course_id; $dbrec->shortname = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['SHORTNAME']['0']['#']); $dbrec->fullname = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['FULLNAME']['0']['#']); $oldobj = backup_getid($restore->backup_unique_code, "scale", backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['SCALEID']['0']['#'])); $dbrec->scaleid = $oldobj->new_id; $dbrec->description = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['DESCRIPTION']['0']['#']); insert_record('grade_outcomes_history', $dbrec); unset($dbrec); } //Increment counters $counter++; //Do some output if ($counter % 1 == 0) { if (!defined('RESTORE_SILENTLY')) { echo "."; if ($counter % 20 == 0) { echo "<br />"; } } backup_flush(300); } } } } } } if (!defined('RESTORE_SILENTLY')) { //End ul echo '</ul>'; } return $status; }
protected function generate_random_raw_grade($item, $userid) { $grade = new grade_grade(); $grade->itemid = $item->id; $grade->userid = $userid; $grade->grademin = 0; $grade->grademax = 1; $valuetype = "grade{$item->gradetype}"; $grade->rawgrade = rand(0, 1000) / 1000; $grade->insert(); return $grade->rawgrade; }
/** * This is the function which runs when cron calls. */ public function execute() { global $CFG, $DB; echo "BEGIN >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"; /** * A script, to be run overnight, to pull L3VA scores from Leap and generate * the MAG, for each student on specifically-tagged courses, and add it into * our live Moodle. * * @copyright 2014-2015 Paul Vaughan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // Script start time. $time_start = microtime(true); // Null or an int (course's id): run the script only for this course. For testing or one-offs. // TODO: Consider changing $thiscourse as an array, not an integer. $thiscourse = null; // null or e.g. 1234 // TODO: can we use *all* the details in version.php? It would make a lot more sense. $version = '1.0.20'; //$build = '20150128'; $build = get_config('block_leap', 'version'); // Debugging. define('DEBUG', true); // Debugging. define('TRUNCATE_LOG', true); // Truncate the log table. if (TRUNCATE_LOG) { echo 'Truncating block_leap_log...'; $DB->delete_records('block_leap_log', null); echo " done.\n"; } overnight::tlog('GradeTracker script, v' . $version . ', ' . $build . '.', 'hiya'); overnight::tlog('Started at ' . date('c', $time_start) . '.', ' go '); if ($thiscourse) { overnight::tlog('IMPORTANT! Processing only course \'' . $thiscourse . '\'.', 'warn'); } overnight::tlog('', '----'); // Before almost anything has the chance to fail, reset the fail delay setting back to 0. if (DEBUG) { if (!($reset = $DB->set_field('task_scheduled', 'faildelay', 0, array('component' => 'block_leap', 'classname' => '\\block_leap\\task\\overnight')))) { overnight::tlog('Scheduled task "fail delay" could not be reset.', 'warn'); } else { overnight::tlog('Scheduled task "fail delay" reset to 0.', 'dbug'); } } $leap_url = get_config('block_leap', 'leap_url'); $auth_token = get_config('block_leap', 'auth_token'); define('LEAP_API_URL', $leap_url . '/people/%s/views/courses.json?token=' . $auth_token); //overnight::tlog( 'Leap API URL: ' . LEAP_API_URL, 'dbug' ); // Number of decimal places in the processed targets (and elsewhere). define('DECIMALS', 3); // Search term to use when searching for courses to process. define('IDNUMBERLIKE', 'leapcore_%'); //define( 'IDNUMBERLIKE', 'leapcore_test' ); // Category details for the above columns to go into. define('CATNAME', get_string('gradebook:category_title', 'block_leap')); // Include some details. require dirname(__FILE__) . '/../../details.php'; // Logging array for the end-of-script summary. $logging = array('courses' => array(), 'students_processed' => array(), 'students_unique' => array(), 'no_l3va' => array(), 'not_updated' => array(), 'grade_types' => array('btec' => 0, 'a level' => 0, 'gcse' => 0, 'refer and pass' => 0, 'noscale' => 0, 'develop, pass' => 0), 'poor_grades' => array(), 'num' => array('courses' => 0, 'students_processed' => 0, 'students_unique' => 0, 'no_l3va' => 0, 'not_updated' => 0, 'grade_types' => 0, 'grade_types_in_use' => 0, 'poor_grades' => 0)); // Small array to store the GCSE English and maths grades from the JSON. $gcse = array('english' => null, 'maths' => null); // Just for internal use, defines the grade type (int) and what it is (string). $gradetypes = array(0 => 'None', 1 => 'Value', 2 => 'Scale', 3 => 'Text'); // Define the wanted column names (will appear in this order in the Gradebook, initially). // These column names are an integral part of this plugin and should not be changed. $column_names = array(get_string('gradebook:tag', 'block_leap') => get_string('gradebook:tag_desc', 'block_leap')); // Make an array keyed to the column names to store the grades in. $targets = array(); foreach ($column_names as $name => $desc) { $targets[strtolower($name)] = ''; } /** * The next section looks through all courses for those with a properly configured Leap block * and adds it (and the tracking configuration) to the $courses array. */ overnight::tlog('', '----'); $courses = $DB->get_records('course', null, null, 'id,shortname,fullname'); $allcourses = array(); foreach ($courses as $course) { if ($course->id != 1) { $coursecontext = \context_course::instance($course->id); if (!($blockrecord = $DB->get_record('block_instances', array('blockname' => 'leap', 'parentcontextid' => $coursecontext->id)))) { if (DEBUG) { overnight::tlog('No Leap block found for course "' . $course->id . '" (' . $course->shortname . ')', 'dbug'); } continue; } if (!($blockinstance = block_instance('leap', $blockrecord))) { if (DEBUG) { overnight::tlog('No Leap block instance found for course "' . $course->id . '" (' . $course->shortname . ')', 'dbug'); } continue; } if (isset($blockinstance->config->trackertype) && !empty($blockinstance->config->trackertype)) { $course->trackertype = $blockinstance->config->trackertype; $course->scalename = null; $course->scaleid = null; $course->gradeid = null; $allcourses[] = $course; if (DEBUG) { overnight::tlog('Tracker "' . $blockinstance->config->trackertype . '" found in course ' . $course->id . ' (' . $course->shortname . ')', 'dbug'); } } else { if (DEBUG) { overnight::tlog('<Tracker not found in course ' . $course->id . ' (' . $course->shortname . ')', 'dbug'); } } } // END if $course != 1 } // END foreach $courses as $course /* foreach ( $allcourses as $course ) { // Ignore the course with id = 1, as it's the front page. if ( $course->id == 1 ) { continue; } else { // First get course context. $coursecontext = \context_course::instance( $course->id ); $blockrecord = $DB->get_record( 'block_instances', array( 'blockname' => 'leap', 'parentcontextid' => $coursecontext->id ) ); $blockinstance = block_instance( 'leap', $blockrecord ); // Check and add trackertype and coursetype to the $course object. if ( isset( $blockinstance->config->trackertype ) && !empty( $blockinstance->config->trackertype ) ) //!empty( $blockinstance->config->trackertype ) && //isset( $blockinstance->config->coursetype ) && //!empty( $blockinstance->config->coursetype ) ) { $course->trackertype = $blockinstance->config->trackertype; //$course->coursetype = $blockinstance->config->coursetype; // Setting some more variables we'll need in due course. $course->scalename = null; $course->scaleid = null; $course->gradeid = null; // All good, so... $courses[] = $course; overnight::tlog( 'Course \'' . $course->fullname . '\' (' . $course->shortname . ') [' . $course->id . '] added to process list.', 'info'); if ( DEBUG ) { overnight::tlog( json_encode( $course ), 'dbug'); } } } } */ //var_dump($courses); exit(0); /* Example $courses array. array(2) { [0]=> object(stdClass)#91 (7) { ["id"]=> string(1) "2" ["shortname"]=> string(5) "TC101" ["fullname"]=> string(15) "Test Course 101" ["trackertype"]=> string(7) "english" ["coursetype"]=> string(14) "a2_englishlang" ["scalename"]=> string(0) "" ["scaleid"]=> NULL } [1]=> object(stdClass)#92 (7) { ["id"]=> string(1) "3" ["shortname"]=> string(5) "TC201" ["fullname"]=> string(15) "Test course 201" ["trackertype"]=> string(7) "english" ["coursetype"]=> string(6) "a2_law" ["scalename"]=> string(0) "" ["scaleid"]=> NULL } } */ $num_courses = count($allcourses); $cur_courses = 0; if ($num_courses == 0) { overnight::tlog('No courses found to process, so halting.', 'EROR'); // Returning false indicates failure. We didn't fail, just found no courses to process. return true; } overnight::tlog('', '----'); /** * Sets up each configured course with a category and columns within it. */ foreach ($allcourses as $course) { $cur_courses++; overnight::tlog('Processing course (' . $cur_courses . '/' . $num_courses . ') ' . $course->fullname . ' (' . $course->shortname . ') [' . $course->id . '] at ' . date('c', time()) . '.', 'info'); $logging['courses'][] = $course->fullname . ' (' . $course->shortname . ') [' . $course->id . '].'; //overnight::tlog( $course->coursetype, 'PVDB' ); /* We need to give serious thought to NOT doing this, as it's basically impossible to set the correct scale at this point. Grades and that: Develop / Pass - would be used for all pass only type qualifications but develop will be used instead of refer, retake, fail etc. U, G, F, E, D, C, B, A, A* - to be used for GCSE / A-level plus any others that have letter grades. Refer, Pass, Merit, Distinction. Refer, PP, PM, MM, MD, DD Refer, PPP, PPM, PMM, MMM, MMD, MDD, DDD Numbers from 0 - 100 - in traffic light systems the numbers will be compared and anything lower than the target will be red, higher than target will be green. */ /* // Work out the scale from the course type. if ( stristr( $course->coursetype, 'as_' ) || stristr( $course->coursetype, 'a2_' ) ) { $course->scalename = 'A Level'; } else if ( stristr( $course->coursetype, 'gcse_' ) ) { $course->scalename = 'GCSE'; } else if ( stristr( $course->coursetype, 'btec_' ) ) { $course->scalename = 'BTEC'; } overnight::tlog( 'Course ' . $course->id . ' appears to be a ' . $course->scalename . ' course.', 'info' ); // Get the scale ID. if ( !$moodlescale = $DB->get_record( 'scale', array( 'name' => $course->scalename ), 'id' ) ) { overnight::tlog( '- Could not find a scale called \'' . $course->scalename . '\' for course ' . $course->id . '.', 'warn' ); } else { // Scale located. $course->scaleid = $moodlescale->id; overnight::tlog( '- Scale called \'' . $course->scalename . '\' found with ID ' . $moodlescale->id . '.', 'info' ); } overnight::tlog( json_encode( $course ), '>dbg'); //var_dump($courses); exit(0); */ overnight::tlog(json_encode($course), '>dbg'); // Figure out the grade type and scale here, pulled directly from the course's gradebook's course itemtype. $coursegradescale = $DB->get_record('grade_items', array('courseid' => $course->id, 'itemtype' => 'course'), 'gradetype, scaleid'); $course->gradeid = $coursegradescale->gradetype; $course->scaleid = $coursegradescale->scaleid; overnight::tlog(json_encode($course), 'PVDB'); if ($course->gradeid == 2) { if ($coursescale = $DB->get_record('scale', array('id' => $course->scaleid))) { $course->scalename = $coursescale->name; $tolog = '- Scale \'' . $course->scaleid . '\' (' . $coursescale->name . ') found [' . $coursescale->scale . ']'; $tolog .= $coursescale->courseid ? ' (which is specific to course ' . $coursescale->courseid . ').' : ' (which is global).'; overnight::tlog($tolog, 'info'); } else { // If the scale doesn't exist that the course is using, this is a problem. overnight::tlog('- Gradetype \'2\' set, but no matching scale found.', 'warn'); } } overnight::tlog(json_encode($course), 'PVDB'); /* if ( $coursegradescale = $DB->get_record( 'grade_items', array( 'courseid' => $course->id, 'itemtype' => 'course' ), 'gradetype, scaleid' ) ) { $course->gradeid = $coursegradescale->gradetype; $course->scaleid = $coursegradescale->scaleid; overnight::tlog( 'Gradetype \'' . $course->gradeid . '\' (' . $gradetypes[$course->gradeid] . ') found.', 'info' ); // If the grade type is 2 / scale. if ( $course->gradeid == 2 ) { if ( $coursescale = $DB->get_record( 'scale', array( 'id' => $course->scaleid ) ) ) { $course->scalename = $coursescale->name; //$course->scaleid = $scaleid; //$course->coursetype = $coursescale->name; $tolog = '- Scale \'' . $course->scaleid . '\' (' . $course->scalename . ') found'; $tolog .= ( $coursescale->courseid ) ? ' (which is specific to course ' . $coursescale->courseid . ').' : ' (which is global).'; overnight::tlog( $tolog, 'info' ); } else { // If the scale doesn't exist that the course is using, this is a problem. overnight::tlog( '- Gradetype \'2\' set, but no matching scale found.', 'warn' ); } } else if ( $course->gradeid == 1 ) { // If the grade type is 1 / value. $course->scalename = 'noscale'; $course->scaleid = 1; // Already set, above. //$course->coursetype = 'Value'; $tolog = ' Using \'' . $gradetypes[$gradeid] . '\' gradetype.'; } } else { // Set it to default if no good scale could be found/used. $gradeid = 0; $scaleid = 0; overnight::tlog('No \'gradetype\' found, so using defaults instead.', 'info'); } // You may get errors here (unknown index IIRC) if no scalename is generated because a scale (or anything) hasn't been set // for that course (e.g. 'cos it's a new course). Catch this earlier! $logging['grade_types'][strtolower($course->scalename)]++; /** * Category checking: create or skip. */ if ($DB->get_record('grade_categories', array('courseid' => $course->id, 'fullname' => CATNAME))) { // Category exists, so skip creation. overnight::tlog('Category \'' . CATNAME . '\' already exists for course ' . $course->id . '.', 'skip'); } else { $grade_category = new \grade_category(); // Create a category for this course. $grade_category->courseid = $course->id; // Course id. $grade_category->fullname = CATNAME; // Set the category name (no description). $grade_category->sortorder = 1; // Need a better way of changing column order. $grade_category->hidden = 1; // Attempting to hide the totals. // Save all that... if (!($gc = $grade_category->insert())) { overnight::tlog('Category \'' . CATNAME . '\' could not be inserted for course ' . $course->id . '.', 'EROR'); return false; } else { overnight::tlog('Category \'' . CATNAME . '\' (' . $gc . ') created for course ' . $course->id . '.'); } } // We've either checked a category exists or created one, so this *should* always work. $cat_id = $DB->get_record('grade_categories', array('courseid' => $course->id, 'fullname' => CATNAME)); $cat_id = $cat_id->id; // One thing we need to do is set 'gradetype' to 0 on that newly created category, which prevents a category total showing // and the grades counting towards the total course grade. $DB->set_field_select('grade_items', 'gradetype', 0, "courseid = " . $course->id . " AND itemtype = 'category' AND iteminstance = " . $cat_id); /** * Column checking: create or update. */ // Step through each column name. foreach ($column_names as $col_name => $col_desc) { // Need to check for previously-created columns and force an update if they already exist. //if ( $DB->get_record('grade_items', array( 'courseid' => $course->id, 'itemname' => $col_name, 'itemtype' => 'manual' ) ) ) { // // Column exists, so update instead. // overnight::tlog('- Column \'' . $col_name . '\' already exists for course ' . $course->id . '.', 'skip'); //} else { $grade_item = new \grade_item(); // Create a new item object. $grade_item->courseid = $course->id; // Course id. $grade_item->itemtype = 'manual'; // Set the category name (no description). $grade_item->itemname = $col_name; // The item's name. $grade_item->iteminfo = $col_desc; // Description of the item. $grade_item->categoryid = $cat_id; // Set the immediate parent category. $grade_item->hidden = 0; // Don't want it hidden (by default). $grade_item->locked = 1; // Lock it (by default). // Per-column specifics. if ($col_name == 'TAG') { $grade_item->sortorder = 1; // In-category sort order. //$grade_item->gradetype = $course->gradeid; $grade_item->gradetype = 3; // Text field. //$grade_item->scaleid = $course->scaleid; //$grade_item->display = 1; // 'Real'. MIGHT need to seperate out options for BTEC and A Level. $grade_item->display = 0; // No frills. } //if ( $col_name == 'L3VA' ) { // // Lock the L3VA col as it's calculated elsewhere. // $grade_item->sortorder = 2; // $grade_item->locked = 1; // $grade_item->decimals = 0; // $grade_item->display = 1; // 'Real'. //} //if ( $col_name == 'MAG' ) { // $grade_item->sortorder = 3; // //$grade_item->locked = 1; // $grade_item->gradetype = $gradeid; // $grade_item->scaleid = $scaleid; // $grade_item->display = 1; // 'Real'. //} // Scale ID, generated earlier. An int, 0 or greater. // TODO: Check if we need this any more!! //$grade_item->scale = $course->scaleid; // Check to see if this record already exists, determining if we insert or update. if (!($grade_items_exists = $DB->get_record('grade_items', array('courseid' => $course->id, 'itemname' => $col_name, 'itemtype' => 'manual')))) { // INSERT a new record. if (!($gi = $grade_item->insert())) { overnight::tlog('- Column \'' . $col_name . '\' could not be inserted for course ' . $course->id . '.', 'EROR'); return false; } else { overnight::tlog('- Column \'' . $col_name . '\' created for course ' . $course->id . '.'); } } else { // UPDATE the existing record. $grade_item->id = $grade_items_exists->id; if (!($gi = $grade_item->update())) { overnight::tlog('- Column \'' . $col_name . '\' could not be updated for course ' . $course->id . '.', 'EROR'); return false; } else { overnight::tlog('- Column \'' . $col_name . '\' updated for course ' . $course->id . '.'); } } //} // END skip processing if manual column(s) already found in course. } // END while working through each rquired column. // Good to here. /** * Move the category to the first location in the gradebook if it isn't already. */ //$gtree = new grade_tree($course->id, false, false); //$temp = grade_edit_tree::move_elements(1, '') /** * Collect enrolments based on each of those courses */ // EPIC 'get enrolled students' query from Stack Overflow: // http://stackoverflow.com/questions/22161606/sql-query-for-courses-enrolment-on-moodle // Only selects manually enrolled, not self-enrolled student roles (redacted!). $sql = "SELECT DISTINCT u.id AS userid, firstname, lastname, username\n FROM mdl_user u\n JOIN mdl_user_enrolments ue ON ue.userid = u.id\n JOIN mdl_enrol e ON e.id = ue.enrolid\n -- AND e.enrol = 'leap'\n JOIN mdl_role_assignments ra ON ra.userid = u.id\n JOIN mdl_context ct ON ct.id = ra.contextid\n AND ct.contextlevel = 50\n JOIN mdl_course c ON c.id = ct.instanceid\n AND e.courseid = c.id\n JOIN mdl_role r ON r.id = ra.roleid\n AND r.shortname = 'student'\n WHERE courseid = " . $course->id . "\n AND e.status = 0\n AND u.suspended = 0\n AND u.deleted = 0\n AND (\n ue.timeend = 0\n OR ue.timeend > NOW()\n )\n AND ue.status = 0\n ORDER BY userid ASC;"; if (!($enrollees = $DB->get_records_sql($sql))) { overnight::tlog('No enrolled students found for course ' . $course->id . '.', 'warn'); } else { $num_enrollees = count($enrollees); overnight::tlog('Found ' . $num_enrollees . ' students enrolled onto course ' . $course->id . '.', 'info'); // A variable to store which enrollee we're processing. $cur_enrollees = 0; foreach ($enrollees as $enrollee) { $cur_enrollees++; // Attempt to extract the student ID from the username. $tmp = explode('@', $enrollee->username); $enrollee->studentid = $tmp[0]; // A proper student, hopefully. overnight::tlog('- Processing user (' . $cur_enrollees . '/' . $num_enrollees . ') ' . $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->userid . ') [' . $enrollee->studentid . '] on course ' . $course->id . '.', 'info'); $logging['students_processed'][] = $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->studentid . ') [' . $enrollee->userid . '] on course ' . $course->id . '.'; $logging['students_unique'][$enrollee->userid] = $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->studentid . ') [' . $enrollee->userid . '].'; // Assemble the URL with the correct data. $leapdataurl = sprintf(LEAP_API_URL, $enrollee->studentid); //if ( DEBUG ) { // overnight::tlog('-- Leap URL: ' . $leapdataurl, 'dbug'); //} // Use fopen to read from the API. if (!($handle = fopen($leapdataurl, 'r'))) { // If the API can't be reached for some reason. overnight::tlog('- Cannot open ' . $leapdataurl . '.', 'EROR'); } else { // API reachable, get the data. $leapdata = fgets($handle); fclose($handle); if (DEBUG) { overnight::tlog('-- Returned JSON: ' . $leapdata, 'dbug'); } // Handle an empty result from the API. if (strlen($leapdata) == 0) { overnight::tlog('-- API returned 0 bytes.', 'EROR'); } else { // Decode the JSON into an object. $leapdata = json_decode($leapdata); // Checking for JSON decoding errors, seems only right. if (json_last_error()) { overnight::tlog('-- JSON decoding returned error code ' . json_last_error() . ' for user ' . $enrollee->studentid . '.', 'EROR'); } else { // We have a L3VA score! And possibly GCSE English and maths grades too. //$targets['l3va'] = number_format( $leapdata->person->l3va, DECIMALS ); //$gcse['english'] = $leapdata->person->gcse_english; //$gcse['maths'] = $leapdata->person->gcse_maths; //if ( $targets['l3va'] == '' || !is_numeric( $targets['l3va'] ) || $targets['l3va'] <= 0 ) { // // If the L3VA isn't good. // overnight::tlog('-- L3VA is not good: \'' . $targets['l3va'] . '\'.', 'warn'); // $logging['no_l3va'][$enrollee->userid] = $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->studentid . ') [' . $enrollee->userid . '].'; // //} else { //overnight::tlog('-- ' . $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->userid . ') [' . $enrollee->studentid . '] L3VA score: ' . $targets['l3va'] . '.', 'info'); overnight::tlog('-- ' . $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->userid . ') [' . $enrollee->studentid . '].', 'info'); // If this course is tagged as a GCSE English or maths course, use the grades supplied in the JSON. /* if ( $course->coursetype == 'leapcore_gcse_english' ) { $magtemp = overnight::make_mag( $gcse['english'], $course->coursetype, $course->scalename ); $tagtemp = array( null, null ); } else if ( $course->coursetype == 'leapcore_gcse_maths' ) { $magtemp = overnight::make_mag( $gcse['maths'], $course->coursetype, $course->scalename ); $tagtemp = array( null, null ); } else { // Make the MAG from the L3VA. $magtemp = overnight::make_mag( $targets['l3va'], $course->coursetype, $course->scalename ); // Make the TAG in the same way, setting 'true' at the end for the next grade up. $tagtemp = overnight::make_mag( $targets['l3va'], $course->coursetype, $course->scalename, true ); } $targets['mag'] = $magtemp[0]; $targets['tag'] = $tagtemp[0]; */ /* if ( $course->coursetype == 'leapcore_gcse_english' || $course->coursetype == 'leapcore_gcse_maths' ) { overnight::tlog('--- GCSEs passed through from Leap JSON: MAG: \'' . $targets['mag'] . '\' ['. $magtemp[1] .']. TAG: \'' . $targets['tag'] . '\' ['. $tagtemp[1] .'].', 'info'); } else { overnight::tlog('--- Generated data: MAG: \'' . $targets['mag'] . '\' ['. $magtemp[1] .']. TAG: \'' . $targets['tag'] . '\' ['. $tagtemp[1] .'].', 'info'); } if ( $targets['mag'] == '0' || $targets['mag'] == '1' ) { $logging['poor_grades'][] = 'MAG ' . $targets['mag'] . ' assigned to ' . $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->studentid . ') [' . $enrollee->userid . '] on course ' . $course->id . '.'; } if ( $targets['tag'] == '0' || $targets['tag'] == '1' ) { $logging['poor_grades'][] = 'TAG ' . $targets['tag'] . ' assigned to ' . $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->studentid . ') [' . $enrollee->userid . '] on course ' . $course->id . '.'; } */ // Loop through all settable, updateable grades. foreach ($targets as $target => $score) { // Need the grade_items.id for grade_grades.itemid. $gradeitem = $DB->get_record('grade_items', array('courseid' => $course->id, 'itemname' => strtoupper($target)), 'id, categoryid'); // Check to see if this data already exists in the database, so we can insert or update. $gradegrade = $DB->get_record('grade_grades', array('itemid' => $gradeitem->id, 'userid' => $enrollee->userid), 'id'); // New grade_grade object. $grade = new \grade_grade(); $grade->userid = $enrollee->userid; $grade->itemid = $gradeitem->id; $grade->categoryid = $gradeitem->categoryid; $grade->rawgrade = $score; // Will stay as set. $grade->finalgrade = $score; // Will change with the grade, e.g. 3. $grade->timecreated = time(); $grade->timemodified = $grade->timecreated; // TODO: "excluded" is a thing and prevents a grade being aggregated. $grade->excluded = true; // If no id exists, INSERT. if (!$gradegrade) { if (!($gl = $grade->insert())) { overnight::tlog('--- ' . strtoupper($target) . ' insert failed for user ' . $enrollee->userid . ' on course ' . $course->id . '.', 'EROR'); } else { overnight::tlog('--- ' . strtoupper($target) . ' (' . $score . ') inserted for user ' . $enrollee->userid . ' on course ' . $course->id . '.'); } } else { // If the row already exists, UPDATE, but don't ever *update* the TAG. //if ( $target == 'mag' && !$score ) { // // For MAGs, we don't want to update to a zero or null score as that may overwrite a manually-entered MAG. // overnight::tlog('--- ' . strtoupper( $target ) . ' of 0 or null (' . $score . ') purposefully not updated for user ' . $enrollee->userid . ' on course ' . $course->id . '.' ); // $logging['not_updated'][] = $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->studentid . ') [' . $enrollee->userid . '] on course ' . $course->id . ': ' . strtoupper( $target ) . ' of \'' . $score . '\'.'; //} else if ( $target != 'tag' ) { $grade->id = $gradegrade->id; // We don't want to set this again, but we do want the modified time set. unset($grade->timecreated); $grade->timemodified = time(); if (!($gl = $grade->update())) { overnight::tlog('--- ' . strtoupper($target) . ' update failed for user ' . $enrollee->userid . ' on course ' . $course->id . '.', 'EROR'); } else { overnight::tlog('--- ' . strtoupper($target) . ' (' . $score . ') update for user ' . $enrollee->userid . ' on course ' . $course->id . '.'); } //} else { // overnight::tlog('--- ' . strtoupper( $target ) . ' purposefully not updated for user ' . $enrollee->userid . ' on course ' . $course->id . '.', 'skip' ); // $logging['not_updated'][] = $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->studentid . ') [' . $enrollee->userid . '] on course ' . $course->id . ': ' . strtoupper( $target ) . ' of \'' . $score . '\'.'; //} // END ignore updating the TAG. } // END insert or update check. } // END foreach loop. //} // END L3VA check. } // END any json_decode errors. } // END empty API result. } // END open leap API for reading. } // END cycle through each course enrollee. } // END enrollee query. // Final blank-ish log entry to separate out one course from another. overnight::tlog('', '----'); } // END foreach course tagged 'leapcore_*'. // Sort and dump the summary log. overnight::tlog('Summary of all performed operations.', 'smry'); asort($logging['courses']); asort($logging['students_processed']); asort($logging['students_unique']); asort($logging['no_l3va']); asort($logging['not_updated']); arsort($logging['grade_types']); asort($logging['poor_grades']); // Processing. $logging['num']['courses'] = count($logging['courses']); $logging['num']['students_processed'] = count($logging['students_processed']); $logging['num']['students_unique'] = count($logging['students_unique']); $logging['num']['no_l3va'] = count($logging['no_l3va']); $logging['num']['not_updated'] = count($logging['not_updated']); $logging['num']['grade_types'] = count($logging['grade_types']); foreach ($logging['grade_types'] as $value) { $logging['num']['grade_types_in_use'] += $value; } $logging['num']['poor_grades'] = count($logging['poor_grades']); if ($logging['num']['courses']) { overnight::tlog($logging['num']['courses'] . ' courses:', 'smry'); $count = 0; foreach ($logging['courses'] as $course) { overnight::tlog('- ' . sprintf('%4s', ++$count) . ': ' . $course, 'smry'); } } else { overnight::tlog('No courses processed.', 'warn'); } if ($logging['num']['students_processed']) { overnight::tlog($logging['num']['students_processed'] . ' student-courses processed:', 'smry'); $count = 0; foreach ($logging['students_processed'] as $student) { overnight::tlog('- ' . sprintf('%4s', ++$count) . ': ' . $student, 'smry'); } } else { overnight::tlog('No student-courses processed.', 'warn'); } if ($logging['num']['students_unique']) { overnight::tlog($logging['num']['students_unique'] . ' unique students:', 'smry'); $count = 0; foreach ($logging['students_unique'] as $student) { echo sprintf('%4s', ++$count) . ': ' . $student . "\n"; overnight::tlog('- ' . sprintf('%4s', $count) . ': ' . $student, 'smry'); } } else { overnight::tlog('No unique students processed.', 'warn'); } if ($logging['num']['no_l3va']) { overnight::tlog($logging['num']['no_l3va'] . ' students with no L3VA:', 'smry'); $count = 0; foreach ($logging['no_l3va'] as $no_l3va) { echo sprintf('%4s', ++$count) . ': ' . $no_l3va . "\n"; overnight::tlog('- ' . sprintf('%4s', $count) . ': ' . $no_l3va, 'smry'); } } else { overnight::tlog('No missing L3VAs.', 'warn'); } if ($logging['num']['not_updated']) { overnight::tlog($logging['num']['not_updated'] . ' students purposefully not updated (0 or null grade):', 'smry'); $count = 0; foreach ($logging['not_updated'] as $not_updated) { overnight::tlog('- ' . sprintf('%4s', ++$count) . ': ' . $not_updated, 'smry'); } } else { overnight::tlog('No students purposefully not updated.', 'warn'); } if ($logging['num']['grade_types']) { overnight::tlog($logging['num']['grade_types'] . ' grade types with ' . $logging['num']['grade_types_in_use'] . ' grades set:', 'smry'); $count = 0; foreach ($logging['grade_types'] as $grade_type => $num_grades) { overnight::tlog('- ' . sprintf('%4s', ++$count) . ': ' . $grade_type . ': ' . $num_grades, 'smry'); } } else { overnight::tlog('No grade_types found.', 'warn'); } if ($logging['num']['poor_grades']) { overnight::tlog($logging['num']['poor_grades'] . ' poor grades:', 'smry'); $count = 0; foreach ($logging['poor_grades'] as $poorgrade) { overnight::tlog('- ' . sprintf('%4s', ++$count) . ': ' . $poorgrade, 'smry'); } } else { overnight::tlog('No poor grades found. Good!', 'smry'); } // Finish time. $time_end = microtime(true); $duration = $time_end - $time_start; $mins = floor($duration / 60) == 0 ? '' : floor($duration / 60) . ' minutes'; $secs = $duration % 60 == 0 ? '' : $duration % 60 . ' seconds'; $secs = $mins == '' ? $secs : ' ' . $secs; overnight::tlog('', '----'); overnight::tlog('Finished at ' . date('c', $time_end) . ', took ' . $mins . $secs . ' (' . number_format($duration, DECIMALS) . ' seconds).', 'byby'); //exit(0); return true; echo "\nEND >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"; }
/** * Updates final grade value for given user, this is a only way to update final * grades from gradebook and import because it logs the change in history table * and deals with overridden flag. This flag is set to prevent later overriding * from raw grades submitted from modules. * * @param int $userid the graded user * @param mixed $finalgrade float value of final grade - false means do not change * @param string $howmodified modification source * @param string $note optional note * @param mixed $feedback teachers feedback as string - false means do not change * @param int $feedbackformat * @return boolean success */ function update_final_grade($userid, $finalgrade = false, $source = NULL, $feedback = false, $feedbackformat = FORMAT_MOODLE, $usermodified = null) { global $USER, $CFG; $result = true; // no grading used or locked if ($this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) { return false; } $grade = new grade_grade(array('itemid' => $this->id, 'userid' => $userid)); $grade->grade_item =& $this; // prevent db fetching of this grade_item if (empty($usermodified)) { $grade->usermodified = $USER->id; } else { $grade->usermodified = $usermodified; } if ($grade->is_locked()) { // do not update locked grades at all return false; } $locktime = $grade->get_locktime(); if ($locktime and $locktime < time()) { // do not update grades that should be already locked, force regrade instead $this->force_regrading(); return false; } $oldgrade = new object(); $oldgrade->finalgrade = $grade->finalgrade; $oldgrade->overridden = $grade->overridden; $oldgrade->feedback = $grade->feedback; $oldgrade->feedbackformat = $grade->feedbackformat; // changed grade? if ($finalgrade !== false) { if ($this->is_overridable_item()) { $grade->overridden = time(); } else { $grade->overridden = 0; } $grade->finalgrade = $this->bounded_grade($finalgrade); } // do we have comment from teacher? if ($feedback !== false) { if ($this->is_overridable_item_feedback()) { // external items (modules, plugins) may have own feedback $grade->overridden = time(); } $grade->feedback = $feedback; $grade->feedbackformat = $feedbackformat; } // HACK: Bob Puffer to allow accurate max score to be inserted into the grade_item record $grade->rawgrademax = $this->grademax; // END OF HACK if (empty($grade->id)) { $grade->timecreated = null; // hack alert - date submitted - no submission yet $grade->timemodified = time(); // hack alert - date graded $result = (bool) $grade->insert($source); } else { if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade) or $grade->feedback !== $oldgrade->feedback or $grade->feedbackformat != $oldgrade->feedbackformat or $grade->overridden != $oldgrade->overridden) { $grade->timemodified = time(); // hack alert - date graded $result = $grade->update($source); } else { // no grade change return $result; } } if (!$result) { // something went wrong - better force final grade recalculation $this->force_regrading(); } else { if ($this->is_course_item() and !$this->needsupdate) { if (grade_regrade_final_grades($this->courseid, $userid, $this) !== true) { $this->force_regrading(); } } else { if (!$this->needsupdate) { $course_item = grade_item_local::fetch_course_item($this->courseid); if (!$course_item->needsupdate) { if (grade_regrade_final_grades_local($this->courseid, $userid, $this) !== true) { $this->force_regrading(); } } else { $this->force_regrading(); } } } } return $result; }
// Check to see if this data already exists in the database, so we can insert or update. $gradegrade = $DB->get_record('grade_grades', array('itemid' => $gradeitem->id, 'userid' => $enrollee->userid), 'id'); // New grade_grade object. $grade = new grade_grade(); $grade->userid = $enrollee->userid; $grade->itemid = $gradeitem->id; $grade->categoryid = $gradeitem->categoryid; $grade->rawgrade = $score; // Will stay as set. $grade->finalgrade = $score; // Will change with the grade, e.g. 3. $grade->timecreated = time(); $grade->timemodified = $grade->timecreated; // If no id exists, INSERT. if (!$gradegrade) { if (!($gl = $grade->insert())) { tlog(' ' . strtoupper($target) . ' insert failed for user ' . $enrollee->userid . ' on course ' . $course->id . '.', 'EROR'); } else { tlog(' ' . strtoupper($target) . ' (' . $score . ') inserted for user ' . $enrollee->userid . ' on course ' . $course->id . '.'); } } else { // If the row already exists, UPDATE, but don't ever *update* the TAG. if ($target == 'mag' && !$score) { // For MAGs, we don't want to update to a zero or null score as that may overwrite a manually-entered MAG. tlog(' ' . strtoupper($target) . ' of 0 or null (' . $score . ') purposefully not updated for user ' . $enrollee->userid . ' on course ' . $course->id . '.'); $logging['not_updated'][] = $enrollee->firstname . ' ' . $enrollee->lastname . ' (' . $enrollee->studentid . ') [' . $enrollee->userid . '] on course ' . $course->id . ': ' . strtoupper($target) . ' of \'' . $score . '\'.'; } else { if ($target != 'tag') { $grade->id = $gradegrade->id; // We don't want to set this again, but we do want the modified time set. unset($grade->timecreated);
/** * internal function for category grades aggregation */ function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded) { global $CFG; if (empty($userid)) { //ignore first call return; } if ($oldgrade) { $grade = new grade_grade($oldgrade, false); $grade->grade_item =& $this->grade_item; } else { // insert final grade - it will be needed later anyway $grade = new grade_grade(array('itemid' => $this->grade_item->id, 'userid' => $userid), false); $grade->insert('system'); $grade->grade_item =& $this->grade_item; $oldgrade = new object(); $oldgrade->finalgrade = $grade->finalgrade; $oldgrade->rawgrade = $grade->rawgrade; $oldgrade->rawgrademin = $grade->rawgrademin; $oldgrade->rawgrademax = $grade->rawgrademax; $oldgrade->rawscaleid = $grade->rawscaleid; } // no need to recalculate locked or overridden grades if ($grade->is_locked() or $grade->is_overridden()) { return; } // can not use own final category grade in calculation unset($grade_values[$this->grade_item->id]); // if no grades calculation possible or grading not allowed clear both final and raw if (empty($grade_values) or empty($items) or $this->grade_item->gradetype != GRADE_TYPE_VALUE and $this->grade_item->gradetype != GRADE_TYPE_SCALE) { $grade->finalgrade = null; $grade->rawgrade = null; if ($grade->finalgrade !== $oldgrade->finalgrade or $grade->rawgrade !== $oldgrade->rawgrade) { $grade->update('system'); } return; } /// normalize the grades first - all will have value 0...1 // ungraded items are not used in aggregation foreach ($grade_values as $itemid => $v) { if (is_null($v)) { // null means no grade unset($grade_values[$itemid]); continue; } else { if (in_array($itemid, $excluded)) { unset($grade_values[$itemid]); continue; } } $grade_values[$itemid] = grade_grade::standardise_score($v, $items[$itemid]->grademin, $items[$itemid]->grademax, 0, 1); } // If global aggregateonlygraded is set, override category value if ($CFG->grade_aggregateonlygraded != -1) { $this->aggregateonlygraded = $CFG->grade_aggregateonlygraded; } // use min grade if grade missing for these types if (!$this->aggregateonlygraded) { foreach ($items as $itemid => $value) { if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) { $grade_values[$itemid] = 0; } } } // limit and sort $this->apply_limit_rules($grade_values); asort($grade_values, SORT_NUMERIC); // let's see we have still enough grades to do any statistics if (count($grade_values) == 0) { // not enough attempts yet $grade->finalgrade = null; $grade->rawgrade = null; if ($grade->finalgrade !== $oldgrade->finalgrade or $grade->rawgrade !== $oldgrade->rawgrade) { $grade->update('system'); } return; } /// start the aggregation switch ($this->aggregation) { case GRADE_AGGREGATE_MEDIAN: // Middle point value in the set: ignores frequencies $num = count($grade_values); $grades = array_values($grade_values); if ($num % 2 == 0) { $agg_grade = ($grades[intval($num / 2) - 1] + $grades[intval($num / 2)]) / 2; } else { $agg_grade = $grades[intval($num / 2 - 0.5)]; } break; case GRADE_AGGREGATE_MIN: $agg_grade = reset($grade_values); break; case GRADE_AGGREGATE_MAX: $agg_grade = array_pop($grade_values); break; case GRADE_AGGREGATE_MODE: // the most common value, average used if multimode $freq = array_count_values($grade_values); arsort($freq); // sort by frequency keeping keys $top = reset($freq); // highest frequency count $modes = array_keys($freq, $top); // search for all modes (have the same highest count) rsort($modes, SORT_NUMERIC); // get highes mode $agg_grade = reset($modes); break; case GRADE_AGGREGATE_WEIGHTED_MEAN: // Weighted average of all existing final grades $weightsum = 0; $sum = 0; foreach ($grade_values as $itemid => $grade_value) { if ($items[$itemid]->aggregationcoef <= 0) { continue; } $weightsum += $items[$itemid]->aggregationcoef; $sum += $items[$itemid]->aggregationcoef * $grade_value; } if ($weightsum == 0) { $agg_grade = null; } else { $agg_grade = $sum / $weightsum; } break; case GRADE_AGGREGATE_EXTRACREDIT_MEAN: // special average $num = 0; $sum = 0; foreach ($grade_values as $itemid => $grade_value) { if ($items[$itemid]->aggregationcoef == 0) { $num += 1; $sum += $grade_value; } else { if ($items[$itemid]->aggregationcoef > 0) { $sum += $items[$itemid]->aggregationcoef * $grade_value; } } } if ($num == 0) { $agg_grade = $sum; // only extra credits or wrong coefs } else { $agg_grade = $sum / $num; } break; case GRADE_AGGREGATE_MEAN: // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum) // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum) default: $num = count($grade_values); $sum = array_sum($grade_values); $agg_grade = $sum / $num; break; } /// prepare update of new raw grade $grade->rawgrademin = $this->grade_item->grademin; $grade->rawgrademax = $this->grade_item->grademax; $grade->rawscaleid = $this->grade_item->scaleid; $grade->rawgrade = null; // categories do not use raw grades // recalculate the rawgrade back to requested range $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $this->grade_item->grademin, $this->grade_item->grademax); if (!is_null($finalgrade)) { $grade->finalgrade = bounded_number($this->grade_item->grademin, $finalgrade, $this->grade_item->grademax); } else { $grade->finalgrade = $finalgrade; } // update in db if changed if ($grade->finalgrade !== $oldgrade->finalgrade or $grade->rawgrade !== $oldgrade->rawgrade or $grade->rawgrademin !== $oldgrade->rawgrademin or $grade->rawgrademax !== $oldgrade->rawgrademax or $grade->rawscaleid !== $oldgrade->rawscaleid) { $grade->update('system'); } return; }
function test_gradebook() { global $DB; $this->resetAfterTest(true); // reset all changes automatically after this test $location_str = 'manual'; // try to get category $grade_category = grade_category::fetch(array('courseid' => $this->courseid, 'fullname' => $this->cat_name)); // NOTE: grade category will not be null but it will be empty $this->assertFalse($grade_category); // create a category $params = new stdClass(); $params->courseid = $this->courseid; $params->fullname = $this->cat_name; $grade_category = new grade_category($params, false); $this->assertTrue(method_exists($grade_category, 'insert')); $grade_category->insert($location_str); // now we will really get the category that we just made $grade_category_fetched = grade_category::fetch(array('courseid' => $this->courseid, 'fullname' => $this->cat_name)); $this->assertTrue($grade_category_fetched !== false); $this->assertEquals($grade_category->id, $grade_category_fetched->id); $this->assertEquals($grade_category->courseid, $grade_category_fetched->courseid); $this->assertEquals($grade_category->path, $grade_category_fetched->path); $this->assertEquals($grade_category->fullname, $grade_category_fetched->fullname); $this->assertEquals($grade_category->parent, $grade_category_fetched->parent); // try to get grade item $grade_item = grade_item::fetch(array('courseid' => $this->courseid, 'categoryid' => $grade_category->id, 'itemname' => $this->item_name)); // NOTE: grade category will not be null but it will be empty $this->assertFalse($grade_item); // create a grade item $grade_item = new grade_item(); $this->assertTrue(method_exists($grade_item, 'insert')); $grade_item->courseid = $this->courseid; $grade_item->categoryid = $grade_category->id; $grade_item->idnumber = $this->item_name; // lookup $grade_item->itemname = $this->item_name; // display $grade_item->itemtype = 'blocks'; $grade_item->itemmodule = 'iclicker'; $grade_item->iteminfo = 'blocks/iclicker for unit testing'; // grademax=100, grademin=0 $grade_item->grademax = 100.0; $grade_item->insert($location_str); // now we will really get the new item $grade_item_fetched = grade_item::fetch(array('courseid' => $this->courseid, 'categoryid' => $grade_category->id, 'itemname' => $this->item_name)); $this->assertTrue($grade_item_fetched !== false); $this->assertEquals($grade_item->id, $grade_item_fetched->id); $this->assertEquals($grade_item->courseid, $grade_item_fetched->courseid); $this->assertEquals($grade_item->categoryid, $grade_item_fetched->categoryid); $this->assertEquals($grade_item->itemname, $grade_item_fetched->itemname); // get empty grades list $all_grades = grade_grade::fetch_all(array('itemid' => $grade_item->id)); $this->assertFalse($all_grades); // add grade $grade_grade = new grade_grade(); $this->assertTrue(method_exists($grade_grade, 'insert')); $grade_grade->itemid = $grade_item->id; $grade_grade->userid = $this->studentid1; $grade_grade->rawgrade = $this->grade_score; $grade_grade->insert($location_str); // get new grade $grade_grade_fetched = grade_grade::fetch(array('itemid' => $grade_item->id, 'userid' => $this->studentid1)); $this->assertTrue($grade_grade_fetched !== false); $this->assertEquals($grade_grade->id, $grade_grade_fetched->id); $this->assertEquals($grade_grade->itemid, $grade_grade_fetched->itemid); $this->assertEquals($grade_grade->userid, $grade_grade_fetched->userid); $this->assertEquals($grade_grade->rawgrade, $grade_grade_fetched->rawgrade); // update the grade $grade_grade->rawgrade = 50; $result = $grade_grade->update($location_str); $this->assertTrue($result); $grade_grade_fetched = grade_grade::fetch(array('id' => $grade_grade->id)); $this->assertTrue($grade_grade_fetched !== false); $this->assertEquals($grade_grade->id, $grade_grade_fetched->id); $this->assertEquals($grade_grade->rawgrade, $grade_grade_fetched->rawgrade); $this->assertEquals(50, $grade_grade_fetched->rawgrade); // get grades $all_grades = grade_grade::fetch_all(array('itemid' => $grade_item->id)); $this->assertTrue($all_grades !== false); $this->assertEquals(1, sizeof($all_grades)); // add more grades $grade_grade2 = new grade_grade(); $grade_grade2->itemid = $grade_item->id; $grade_grade2->userid = $this->studentid2; $grade_grade2->rawgrade = $this->grade_score; $grade_grade2->insert($location_str); // get grades $all_grades = grade_grade::fetch_all(array('itemid' => $grade_item->id)); $this->assertTrue($all_grades !== false); $this->assertEquals(2, sizeof($all_grades)); // make sure this can run $result = $grade_item->regrade_final_grades(); $this->assertTrue($result); // remove grades $this->assertTrue(method_exists($grade_grade, 'delete')); $result = $grade_grade->delete($location_str); $this->assertTrue($result); $result = $grade_grade2->delete($location_str); $this->assertTrue($result); // check no grades left $all_grades = grade_grade::fetch_all(array('itemid' => $grade_item->id)); $this->assertFalse($all_grades); // remove grade item $this->assertTrue(method_exists($grade_item, 'delete')); $result = $grade_item->delete($location_str); $this->assertTrue($result); // remove grade category $this->assertTrue(method_exists($grade_category, 'delete')); $result = $grade_category->delete($location_str); $this->assertTrue($result); $this->resetAfterTest(); }
private static function save_grade_item($grade_item) { if (!$grade_item) { throw new InvalidArgumentException("grade_item must be set"); } if (!$grade_item->courseid) { throw new InvalidArgumentException("grade_item->courseid must be set"); } if (!$grade_item->categoryid) { throw new InvalidArgumentException("grade_item->categoryid must be set"); } if (!$grade_item->name) { throw new InvalidArgumentException("grade_item->name must be set"); } if (!isset($grade_item->item_number)) { $grade_item->item_number = 0; } // check for an existing item and update or create $grade_item_tosave = grade_item::fetch(array('courseid' => $grade_item->courseid, 'itemmodule' => self::GRADE_ITEM_MODULE, 'itemname' => $grade_item->name)); if (!$grade_item_tosave) { // create new one $grade_item_tosave = new grade_item(); $grade_item_tosave->itemmodule = self::GRADE_ITEM_MODULE; $grade_item_tosave->courseid = $grade_item->courseid; $grade_item_tosave->categoryid = $grade_item->categoryid; $grade_item_tosave->iteminfo = $grade_item->typename; //$grade_item_tosave->iteminfo = $grade_item->name.' '.$grade_item->type.' '.self::GRADE_CATEGORY_NAME; $grade_item_tosave->itemnumber = $grade_item->item_number; //$grade_item_tosave->idnumber = $grade_item->name; $grade_item_tosave->itemname = $grade_item->name; $grade_item_tosave->itemtype = self::GRADE_ITEM_TYPE; //$grade_item_tosave->itemmodule = self::GRADE_ITEM_MODULE; if (isset($grade_item->points_possible) && $grade_item->points_possible > 0) { $grade_item_tosave->grademax = $grade_item->points_possible; } $grade_item_tosave->insert(self::GRADE_LOCATION_STR); } else { // update if (isset($grade_item->points_possible) && $grade_item->points_possible > 0) { $grade_item_tosave->grademax = $grade_item->points_possible; } $grade_item_tosave->categoryid = $grade_item->categoryid; $grade_item_tosave->iteminfo = $grade_item->typename; $grade_item_tosave->update(self::GRADE_LOCATION_STR); } $grade_item_id = $grade_item_tosave->id; $grade_item_pp = $grade_item_tosave->grademax; // now save the related scores if (isset($grade_item->scores) && !empty($grade_item->scores)) { // get the existing scores $current_scores = array(); $existing_grades = grade_grade::fetch_all(array('itemid' => $grade_item_id)); if ($existing_grades) { foreach ($existing_grades as $grade) { $current_scores[$grade->userid] = $grade; } } // run through the scores in the gradeitem and try to save them $errors_count = 0; $processed_scores = array(); foreach ($grade_item->scores as $score) { $user = self::get_users($score->user_id); if (!$user) { $score->error = self::USER_DOES_NOT_EXIST_ERROR; $processed_scores[] = $score; $errors_count++; continue; } $user_id = $user->id; // null/blank scores are not allowed if (!isset($score->score)) { $score->error = 'NO_SCORE_ERROR'; $processed_scores[] = $score; $errors_count++; continue; } if (!is_numeric($score->score)) { $score->error = 'SCORE_INVALID'; $processed_scores[] = $score; $errors_count++; continue; } $score->score = floatval($score->score); // Student Score should not be greater than the total points possible if ($score->score > $grade_item_pp) { $score->error = self::POINTS_POSSIBLE_UPDATE_ERRORS; $processed_scores[] = $score; $errors_count++; continue; } try { $grade_tosave = null; if (isset($current_scores[$user_id])) { // existing score $grade_tosave = $current_scores[$user_id]; // check against existing score if ($score->score < $grade_tosave->rawgrade) { $score->error = self::SCORE_UPDATE_ERRORS; $processed_scores[] = $score; $errors_count++; continue; } $grade_tosave->finalgrade = $score->score; $grade_tosave->rawgrade = $score->score; $grade_tosave->timemodified = time(); /** @noinspection PhpUndefinedMethodInspection */ $grade_tosave->update(self::GRADE_LOCATION_STR); } else { // new score $grade_tosave = new grade_grade(); $grade_tosave->itemid = $grade_item_id; $grade_tosave->userid = $user_id; $grade_tosave->finalgrade = $score->score; $grade_tosave->rawgrade = $score->score; $grade_tosave->rawgrademax = $grade_item_pp; $now = time(); $grade_tosave->timecreated = $now; $grade_tosave->timemodified = $now; $grade_tosave->insert(self::GRADE_LOCATION_STR); } /** @noinspection PhpUndefinedFieldInspection */ $grade_tosave->user_id = $score->user_id; $processed_scores[] = $grade_tosave; } catch (Exception $e) { // General errors, caused while performing updates (Tag: generalerrors) $score->error = self::GENERAL_ERRORS; $processed_scores[] = $score; $errors_count++; } } /** @noinspection PhpUndefinedFieldInspection */ $grade_item_tosave->scores = $processed_scores; // put the errors in the item if ($errors_count > 0) { $errors = array(); foreach ($processed_scores as $score) { if (isset($score->error)) { $errors[$score->user_id] = $score->error; } } /** @noinspection PhpUndefinedFieldInspection */ $grade_item_tosave->errors = $errors; } $grade_item_tosave->force_regrading(); } return $grade_item_tosave; }
/** * Internal function that does the final grade calculation * * @param int $userid The user ID * @param array $params An array of grade items of the form {'gi'.$itemid]} => $finalgrade * @param array $useditems An array of grade item IDs that this grade item depends on plus its own ID * @param grade_grade $oldgrade A grade_grade instance containing the old values from the database * @return bool False if an error occurred */ public function use_formula($userid, $params, $useditems, $oldgrade) { if (empty($userid)) { return true; } // add missing final grade values // not graded (null) is counted as 0 - the spreadsheet way $allinputsnull = true; foreach ($useditems as $gi) { if (!array_key_exists('gi' . $gi, $params) || is_null($params['gi' . $gi])) { $params['gi' . $gi] = 0; } else { $params['gi' . $gi] = (double) $params['gi' . $gi]; if ($gi != $this->id) { $allinputsnull = false; } } } // can not use own final grade during calculation unset($params['gi' . $this->id]); // Check to see if the gradebook is frozen. This allows grades to not be altered at all until a user verifies that they // wish to update the grades. $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->courseid); $rawminandmaxchanged = false; // insert final grade - will be needed later anyway if ($oldgrade) { // Only run through this code if the gradebook isn't frozen. if ($gradebookcalculationsfreeze && (int) $gradebookcalculationsfreeze <= 20150627) { // Do nothing. } else { // The grade_grade for a calculated item should have the raw grade maximum and minimum set to the // grade_item grade maximum and minimum respectively. if ($oldgrade->rawgrademax != $this->grademax || $oldgrade->rawgrademin != $this->grademin) { $rawminandmaxchanged = true; $oldgrade->rawgrademax = $this->grademax; $oldgrade->rawgrademin = $this->grademin; } } $oldfinalgrade = $oldgrade->finalgrade; $grade = new grade_grade($oldgrade, false); // fetching from db is not needed $grade->grade_item =& $this; } else { $grade = new grade_grade(array('itemid' => $this->id, 'userid' => $userid), false); $grade->grade_item =& $this; $rawminandmaxchanged = false; if ($gradebookcalculationsfreeze && (int) $gradebookcalculationsfreeze <= 20150627) { // Do nothing. } else { // The grade_grade for a calculated item should have the raw grade maximum and minimum set to the // grade_item grade maximum and minimum respectively. $rawminandmaxchanged = true; $grade->rawgrademax = $this->grademax; $grade->rawgrademin = $this->grademin; } $grade->insert('system'); $oldfinalgrade = null; } // no need to recalculate locked or overridden grades if ($grade->is_locked() or $grade->is_overridden()) { return true; } if ($allinputsnull) { $grade->finalgrade = null; $result = true; } else { // do the calculation $this->formula->set_params($params); $result = $this->formula->evaluate(); if ($result === false) { $grade->finalgrade = null; } else { // normalize $grade->finalgrade = $this->bounded_grade($result); } } // Only run through this code if the gradebook isn't frozen. if ($gradebookcalculationsfreeze && (int) $gradebookcalculationsfreeze <= 20150627) { // Update in db if changed. if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) { $grade->timemodified = time(); $success = $grade->update('compute'); // If successful trigger a user_graded event. if ($success) { \core\event\user_graded::create_from_grade($grade)->trigger(); } } } else { // Update in db if changed. if (grade_floats_different($grade->finalgrade, $oldfinalgrade) || $rawminandmaxchanged) { $grade->timemodified = time(); $success = $grade->update('compute'); // If successful trigger a user_graded event. if ($success) { \core\event\user_graded::create_from_grade($grade)->trigger(); } } } if ($result !== false) { //lock grade if needed } if ($result === false) { return false; } else { return true; } }
$alis->insert('report_targetgrades'); } if ($alis_num = grade_grade::fetch(array('itemid' => $itemids['alisnum'], 'userid' => $student->id))) { $alis_num->rawgrade = $mtg['number']; $alis_num->finalgrade = $mtg['number']; $alis_num->timemodified = time(); $alis_num->update('report_targetgrades'); } else { $alis_num = new grade_grade(); $alis_num->itemid = $itemids['alisnum']; $alis_num->userid = $student->id; $alis_num->rawgrade = $mtg['number']; $alis_num->finalgrade = $mtg['number']; $alis_num->timecreated = time(); $alis_num->timemodified = time(); $alis_num->insert('report_targetgrades'); } } catch (tg\no_data_for_student_exception $e) { $empty_students[] = $e->getMessage(); } catch (tg\no_mtg_for_student_exception $e) { $failed_grade_calcs[] = $e->getMessage(); } } } catch (tg\no_students_exception $e) { $empty_courses[] = $e->getMessage(); } catch (tg\no_config_for_course_exception $e) { $unconfigured_courses[] = $e->getMessage(); } if ($regrade) { grade_regrade_final_grades($course->id); tg\sort_gradebook($course);
/** * internal function - does the final grade calculation */ function use_formula($userid, $params, $useditems, $oldgrade) { if (empty($userid)) { return true; } // add missing final grade values // not graded (null) is counted as 0 - the spreadsheet way foreach ($useditems as $gi) { if (!array_key_exists('gi' . $gi, $params)) { $params['gi' . $gi] = 0; } else { $params['gi' . $gi] = (double) $params['gi' . $gi]; } } // can not use own final grade during calculation unset($params['gi' . $this->id]); // insert final grade - will be needed later anyway if ($oldgrade) { $oldfinalgrade = $oldgrade->finalgrade; $grade = new grade_grade($oldgrade, false); // fetching from db is not needed $grade->grade_item =& $this; } else { $grade = new grade_grade(array('itemid' => $this->id, 'userid' => $userid), false); $grade->grade_item =& $this; $grade->insert('system'); $oldfinalgrade = null; } // no need to recalculate locked or overridden grades if ($grade->is_locked() or $grade->is_overridden()) { return true; } // do the calculation $this->formula->set_params($params); $result = $this->formula->evaluate(); if ($result === false) { $grade->finalgrade = null; } else { // normalize $result = bounded_number($this->grademin, $result, $this->grademax); if ($this->gradetype == GRADE_TYPE_SCALE) { $result = round($result + 1.0E-5); // round scales upwards } $grade->finalgrade = $result; } // update in db if changed if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) { $grade->update('compute'); } if ($result !== false) { //lock grade if needed } if ($result === false) { return false; } else { return true; } }
/** * Validate that the method respects the locked status when run for a * specific user */ public function test_methodonlyupdatesunlockedenrolmentsforspecificuserid() { global $DB; $this->load_csv_data(); // Set up enrolments. $this->make_course_enrollable(); enrol_try_internal_enrol(2, 100, 1); enrol_try_internal_enrol(2, 101, 1); // Set required PM course grade. $pmcourse = new \course(array('id' => 100, 'completion_grade' => 50)); $pmcourse->save(); // Set up course grade item. $coursegradeitem = \grade_item::fetch_course_item(2); $coursegradeitem->grademax = 100; $coursegradeitem->needsupdate = false; $coursegradeitem->locked = true; $coursegradeitem->update(); // Assign student grades. $coursegradegrade = new \grade_grade(array('itemid' => 1, 'userid' => 100, 'finalgrade' => 100)); $coursegradegrade->insert(); $coursegradegrade = new \grade_grade(array('itemid' => 1, 'userid' => 101, 'finalgrade' => 100)); $coursegradegrade->insert(); // Enrol the student. $student = new \student(); $student->userid = 103; $student->classid = 100; $student->grade = 0; $student->completestatusid = STUSTATUS_NOTCOMPLETE; $student->locked = 1; $student->save(); // Call and validate that locked record is not changed. $sync = new \local_elisprogram\moodle\synchronize(); $sync->synchronize_moodle_class_grades(100); $this->assert_student_exists(100, 103, 0, STUSTATUS_NOTCOMPLETE, null, null, 1); $DB->execute("UPDATE {" . \student::TABLE . "} SET locked = 0"); // Call and validate that unlocked record is changed. $sync = new \local_elisprogram\moodle\synchronize(); $sync->synchronize_moodle_class_grades(100); // Validate count. $count = $DB->count_records(\student::TABLE, array('completestatusid' => STUSTATUS_PASSED)); $this->assertEquals(1, $count); // NOTE: this method does not lock enrolments. $this->assert_student_exists(100, 103, 100, STUSTATUS_PASSED, null, null, 0); }
/** * Recover a user's grades from grade_grades_history * @param int $userid the user ID whose grades we want to recover * @param int $courseid the relevant course * @return bool true if successful or false if there was an error or no grades could be recovered */ function grade_recover_history_grades($userid, $courseid) { global $CFG, $DB; if ($CFG->disablegradehistory) { debugging('Attempting to recover grades when grade history is disabled.'); return false; } //Were grades recovered? Flag to return. $recoveredgrades = false; //Check the user is enrolled in this course //Dont bother checking if they have a gradeable role. They may get one later so recover //whatever grades they have now just in case. $course_context = get_context_instance(CONTEXT_COURSE, $courseid); if (!is_enrolled($course_context, $userid)) { debugging('Attempting to recover the grades of a user who is deleted or not enrolled. Skipping recover.'); return false; } //Check for existing grades for this user in this course //Recovering grades when the user already has grades can lead to duplicate indexes and bad data //In the future we could move the existing grades to the history table then recover the grades from before then $sql = "SELECT gg.id\n FROM {grade_grades} gg\n JOIN {grade_items} gi ON gi.id = gg.itemid\n WHERE gi.courseid = :courseid AND gg.userid = :userid"; $params = array('userid' => $userid, 'courseid' => $courseid); if ($DB->record_exists_sql($sql, $params)) { debugging('Attempting to recover the grades of a user who already has grades. Skipping recover.'); return false; } else { //Retrieve the user's old grades //have history ID as first column to guarantee we a unique first column $sql = "SELECT h.id, gi.itemtype, gi.itemmodule, gi.iteminstance as iteminstance, gi.itemnumber, h.source, h.itemid, h.userid, h.rawgrade, h.rawgrademax,\n h.rawgrademin, h.rawscaleid, h.usermodified, h.finalgrade, h.hidden, h.locked, h.locktime, h.exported, h.overridden, h.excluded, h.feedback,\n h.feedbackformat, h.information, h.informationformat, h.timemodified, itemcreated.tm AS timecreated\n FROM {grade_grades_history} h\n JOIN (SELECT itemid, MAX(id) AS id\n FROM {grade_grades_history}\n WHERE userid = :userid1\n GROUP BY itemid) maxquery ON h.id = maxquery.id AND h.itemid = maxquery.itemid\n JOIN {grade_items} gi ON gi.id = h.itemid\n JOIN (SELECT itemid, MAX(timemodified) AS tm\n FROM {grade_grades_history}\n WHERE userid = :userid2 AND action = :insertaction\n GROUP BY itemid) itemcreated ON itemcreated.itemid = h.itemid\n WHERE gi.courseid = :courseid"; $params = array('userid1' => $userid, 'userid2' => $userid, 'insertaction' => GRADE_HISTORY_INSERT, 'courseid' => $courseid); $oldgrades = $DB->get_records_sql($sql, $params); //now move the old grades to the grade_grades table foreach ($oldgrades as $oldgrade) { unset($oldgrade->id); $grade = new grade_grade($oldgrade, false); //2nd arg false as dont want to try and retrieve a record from the DB $grade->insert($oldgrade->source); //dont include default empty grades created when activities are created if (!is_null($oldgrade->finalgrade) || !is_null($oldgrade->feedback)) { $recoveredgrades = true; } } } //Some activities require manual grade synching (moving grades from the activity into the gradebook) //If the student was deleted when synching was done they may have grades in the activity that haven't been moved across grade_grab_course_grades($courseid, null, $userid); return $recoveredgrades; }
/** * Internal function that does the final grade calculation * * @param int $userid The user ID * @param array $params An array of grade items of the form {'gi'.$itemid]} => $finalgrade * @param array $useditems An array of grade item IDs that this grade item depends on plus its own ID * @param grade_grade $oldgrade A grade_grade instance containing the old values from the database * @return bool False if an error occurred */ public function use_formula($userid, $params, $useditems, $oldgrade) { if (empty($userid)) { return true; } // add missing final grade values // not graded (null) is counted as 0 - the spreadsheet way $allinputsnull = true; foreach ($useditems as $gi) { if (!array_key_exists('gi' . $gi, $params) || is_null($params['gi' . $gi])) { $params['gi' . $gi] = 0; } else { $params['gi' . $gi] = (double) $params['gi' . $gi]; if ($gi != $this->id) { $allinputsnull = false; } } } // can not use own final grade during calculation unset($params['gi' . $this->id]); // insert final grade - will be needed later anyway if ($oldgrade) { $oldfinalgrade = $oldgrade->finalgrade; $grade = new grade_grade($oldgrade, false); // fetching from db is not needed $grade->grade_item =& $this; } else { $grade = new grade_grade(array('itemid' => $this->id, 'userid' => $userid), false); $grade->grade_item =& $this; $grade->insert('system'); $oldfinalgrade = null; } // no need to recalculate locked or overridden grades if ($grade->is_locked() or $grade->is_overridden()) { return true; } if ($allinputsnull) { $grade->finalgrade = null; $result = true; } else { // do the calculation $this->formula->set_params($params); $result = $this->formula->evaluate(); if ($result === false) { $grade->finalgrade = null; } else { // normalize $grade->finalgrade = $this->bounded_grade($result); } } // update in db if changed if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) { $grade->timemodified = time(); $grade->update('compute'); } if ($result !== false) { //lock grade if needed } if ($result === false) { return false; } else { return true; } }
protected function process_grade_grade($data) { $data = (object)($data); unset($data->id); $data->itemid = $this->get_new_parentid('grade_item'); $data->userid = $this->get_mappingid('user', $data->userid); $data->usermodified = $this->get_mappingid('user', $data->usermodified); $data->rawscaleid = $this->get_mappingid('scale', $data->rawscaleid); // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled? $data->overridden = $this->apply_date_offset($data->overridden); $grade = new grade_grade($data, false); $grade->insert('restore'); // no need to save any grade_grade mapping }
/** * Test that the upgrade script correctly flags courses to be frozen due to letter boundary problems. */ public function test_upgrade_course_letter_boundary() { global $CFG, $DB; $this->resetAfterTest(true); require_once $CFG->libdir . '/db/upgradelib.php'; // Create a user. $user = $this->getDataGenerator()->create_user(); // Create some courses. $courses = array(); $contexts = array(); for ($i = 0; $i < 45; $i++) { $course = $this->getDataGenerator()->create_course(); $context = context_course::instance($course->id); if (in_array($i, array(2, 5, 10, 13, 14, 19, 23, 25, 30, 34, 36))) { // Assign good letter boundaries. $this->assign_good_letter_boundary($context->id); } if (in_array($i, array(3, 6, 11, 15, 20, 24, 26, 31, 35))) { // Assign bad letter boundaries. $this->assign_bad_letter_boundary($context->id); } if (in_array($i, array(3, 9, 10, 11, 18, 19, 20, 29, 30, 31, 40))) { grade_set_setting($course->id, 'displaytype', '3'); } else { if (in_array($i, array(8, 17, 28))) { grade_set_setting($course->id, 'displaytype', '2'); } } if (in_array($i, array(37, 43))) { // Show. grade_set_setting($course->id, 'report_user_showlettergrade', '1'); } else { if (in_array($i, array(38, 42))) { // Hide. grade_set_setting($course->id, 'report_user_showlettergrade', '0'); } } $assignrow = $this->getDataGenerator()->create_module('assign', array('course' => $course->id, 'name' => 'Test!')); $gi = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'assign', 'iteminstance' => $assignrow->id, 'courseid' => $course->id)); if (in_array($i, array(6, 13, 14, 15, 23, 24, 34, 35, 36, 41))) { grade_item::set_properties($gi, array('display' => 3)); $gi->update(); } else { if (in_array($i, array(12, 21, 32))) { grade_item::set_properties($gi, array('display' => 2)); $gi->update(); } } $gradegrade = new grade_grade(); $gradegrade->itemid = $gi->id; $gradegrade->userid = $user->id; $gradegrade->rawgrade = 55.5563; $gradegrade->finalgrade = 55.5563; $gradegrade->rawgrademax = 100; $gradegrade->rawgrademin = 0; $gradegrade->timecreated = time(); $gradegrade->timemodified = time(); $gradegrade->insert(); $contexts[] = $context; $courses[] = $course; } upgrade_course_letter_boundary(); // No system setting for grade letter boundaries. // [0] A course with no letter boundaries. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[0]->id})); // [1] A course with letter boundaries which are default. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[1]->id})); // [2] A course with letter boundaries which are custom but not affected. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[2]->id})); // [3] A course with letter boundaries which are custom and will be affected. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[3]->id}); // [4] A course with no letter boundaries, but with a grade item with letter boundaries which are default. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[4]->id})); // [5] A course with no letter boundaries, but with a grade item with letter boundaries which are not default, but not affected. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[5]->id})); // [6] A course with no letter boundaries, but with a grade item with letter boundaries which are not default which will be affected. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[6]->id}); // System setting for grade letter boundaries (default). set_config('grade_displaytype', '3'); for ($i = 0; $i < 45; $i++) { unset_config('gradebook_calculations_freeze_' . $courses[$i]->id); } upgrade_course_letter_boundary(); // [7] A course with no grade display settings for the course or grade items. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[7]->id})); // [8] A course with grade display settings, but for something that isn't letters. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[8]->id})); // [9] A course with grade display settings of letters which are default. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[9]->id})); // [10] A course with grade display settings of letters which are not default, but not affected. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[10]->id})); // [11] A course with grade display settings of letters which are not default, which will be affected. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[11]->id}); // [12] A grade item with display settings that are not letters. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[12]->id})); // [13] A grade item with display settings of letters which are default. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[13]->id})); // [14] A grade item with display settings of letters which are not default, but not affected. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[14]->id})); // [15] A grade item with display settings of letters which are not default, which will be affected. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[15]->id}); // System setting for grade letter boundaries (custom with problem). $systemcontext = context_system::instance(); $this->assign_bad_letter_boundary($systemcontext->id); for ($i = 0; $i < 45; $i++) { unset_config('gradebook_calculations_freeze_' . $courses[$i]->id); } upgrade_course_letter_boundary(); // [16] A course with no grade display settings for the course or grade items. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[16]->id}); // [17] A course with grade display settings, but for something that isn't letters. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[17]->id})); // [18] A course with grade display settings of letters which are default. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[18]->id}); // [19] A course with grade display settings of letters which are not default, but not affected. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[19]->id})); // [20] A course with grade display settings of letters which are not default, which will be affected. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[20]->id}); // [21] A grade item with display settings which are not letters. Grade total will be affected so should be frozen. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[21]->id}); // [22] A grade item with display settings of letters which are default. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[22]->id}); // [23] A grade item with display settings of letters which are not default, but not affected. Course uses new letter boundary setting. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[23]->id})); // [24] A grade item with display settings of letters which are not default, which will be affected. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[24]->id}); // [25] A course which is using the default grade display setting, but has updated the grade letter boundary (not 57) Should not be frozen. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[25]->id})); // [26] A course that is using the default display setting (letters) and altered the letter boundary with 57. Should be frozen. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[26]->id}); // System setting not showing letters. set_config('grade_displaytype', '2'); for ($i = 0; $i < 45; $i++) { unset_config('gradebook_calculations_freeze_' . $courses[$i]->id); } upgrade_course_letter_boundary(); // [27] A course with no grade display settings for the course or grade items. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[27]->id})); // [28] A course with grade display settings, but for something that isn't letters. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[28]->id})); // [29] A course with grade display settings of letters which are default. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[29]->id}); // [30] A course with grade display settings of letters which are not default, but not affected. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[30]->id})); // [31] A course with grade display settings of letters which are not default, which will be affected. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[31]->id}); // [32] A grade item with display settings which are not letters. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[32]->id})); // [33] All system defaults. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[33]->id})); // [34] A grade item with display settings of letters which are not default, but not affected. Course uses new letter boundary setting. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[34]->id})); // [35] A grade item with display settings of letters which are not default, which will be affected. $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[35]->id}); // [36] A course with grade display settings of letters with modified and good boundary (not 57) Should not be frozen. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[36]->id})); // Previous site conditions still exist. for ($i = 0; $i < 45; $i++) { unset_config('gradebook_calculations_freeze_' . $courses[$i]->id); } upgrade_course_letter_boundary(); // [37] Site setting for not showing the letter column and course setting set to show (frozen). $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[37]->id}); // [38] Site setting for not showing the letter column and course setting set to hide. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[38]->id})); // [39] Site setting for not showing the letter column and course setting set to default. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[39]->id})); // [40] Site setting for not showing the letter column and course setting set to default. Course display set to letters (frozen). $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[40]->id}); // [41] Site setting for not showing the letter column and course setting set to default. Grade item display set to letters (frozen). $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[41]->id}); // Previous site conditions still exist. for ($i = 0; $i < 45; $i++) { unset_config('gradebook_calculations_freeze_' . $courses[$i]->id); } set_config('grade_report_user_showlettergrade', '1'); upgrade_course_letter_boundary(); // [42] Site setting for showing the letter column, but course setting set to hide. $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[42]->id})); // [43] Site setting for showing the letter column and course setting set to show (frozen). $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[43]->id}); // [44] Site setting for showing the letter column and course setting set to default (frozen). $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[44]->id}); }
protected function process_grade_grade($data) { $data = (object) $data; $olduserid = $data->userid; unset($data->id); $data->itemid = $this->get_new_parentid('grade_item'); $data->userid = $this->get_mappingid('user', $data->userid, null); if (!empty($data->userid)) { $data->usermodified = $this->get_mappingid('user', $data->usermodified, null); $data->rawscaleid = $this->get_mappingid('scale', $data->rawscaleid); // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled? $data->overridden = $this->apply_date_offset($data->overridden); $grade = new grade_grade($data, false); $grade->insert('restore'); // no need to save any grade_grade mapping } else { debugging("Mapped user id not found for user id '{$olduserid}', grade item id '{$data->itemid}'"); } }
/** * Validate that the version 1 plugin deletes appropriate associations when * deleting a course */ public function test_version1importdeletecoursedeletesassociations() { global $DB, $CFG, $USER; require_once $CFG->dirroot . '/user/lib.php'; require_once $CFG->dirroot . '/lib/gradelib.php'; require_once $CFG->dirroot . '/group/lib.php'; require_once $CFG->dirroot . '/lib/conditionlib.php'; require_once $CFG->dirroot . '/lib/enrollib.php'; require_once $CFG->dirroot . '/tag/lib.php'; require_once $CFG->dirroot . '/lib/questionlib.php'; // Setup. $initialnumcontexts = $DB->count_records('context', array('contextlevel' => CONTEXT_COURSE)); $DB->delete_records('block_instances'); // Set up the course with one section, including default blocks. set_config('defaultblocks_topics', 'search_forums'); set_config('maxsections', 10, 'moodlecourse'); $this->run_core_course_import(array('shortname' => 'deleteassociationsshortname', 'numsections' => 1)); // Create a user record. $record = new stdClass(); $record->username = '******'; $record->password = '******'; $userid = user_create_user($record); // Create a course-level role. $courseid = $DB->get_field('course', 'id', array('shortname' => 'deleteassociationsshortname')); $coursecontext = context_course::instance($courseid); $roleid = create_role('deleterole', 'deleterole', 'deleterole'); set_role_contextlevels($roleid, array(CONTEXT_COURSE)); $enrol = new stdClass(); $enrol->enrol = 'manual'; $enrol->courseid = $courseid; $enrol->status = ENROL_INSTANCE_ENABLED; if (!$DB->record_exists('enrol', (array) $enrol)) { $DB->insert_record('enrol', $enrol); } // Assign the user to the course-level role. enrol_try_internal_enrol($courseid, $userid, $roleid); // Create a grade item. $gradeitem = new grade_item(array('courseid' => $courseid, 'itemtype' => 'manual', 'itemname' => 'testitem'), false); $gradeitem->insert(); $gradegrade = new grade_grade(array('itemid' => $gradeitem->id, 'userid' => $userid), false); // Assign the user a grade. $gradegrade->insert(); // Create a grade outcome. $gradeoutcome = new grade_outcome(array('courseid' => $courseid, 'shortname' => 'bogusshortname', 'fullname' => 'bogusfullname')); $gradeoutcome->insert(); // Create a grade scale. $gradescale = new grade_scale(array('courseid' => $courseid, 'name' => 'bogusname', 'userid' => $userid, 'scale' => 'bogusscale', 'description' => 'bogusdescription')); $gradescale->insert(); // Set a grade setting value. grade_set_setting($courseid, 'bogus', 'bogus'); // Set up a grade letter. $gradeletter = new stdClass(); $gradeletter->contextid = $coursecontext->id; $gradeletter->lowerboundary = 80; $gradeletter->letter = 'A'; $DB->insert_record('grade_letters', $gradeletter); // Set up a forum instance. $forum = new stdClass(); $forum->course = $courseid; $forum->intro = 'intro'; $forum->id = $DB->insert_record('forum', $forum); // Add it as a course module. $forum->module = $DB->get_field('modules', 'id', array('name' => 'forum')); $forum->instance = $forum->id; $cmid = add_course_module($forum); // Set up a completion record. $completion = new stdClass(); $completion->coursemoduleid = $cmid; $completion->completionstate = 0; $completion->userid = 9999; $completion->timemodified = time(); $DB->insert_record('course_modules_completion', $completion); // Set up a completion condition. $forum->id = $cmid; $ci = new condition_info($forum, CONDITION_MISSING_EVERYTHING, false); $ci->add_completion_condition($cmid, COMPLETION_ENABLED); // Set the blocks position. $instances = $DB->get_records('block_instances', array('parentcontextid' => $coursecontext->id)); $page = new stdClass(); $page->context = $coursecontext; $page->pagetype = 'course-view-*'; $page->subpage = false; foreach ($instances as $instance) { blocks_set_visibility($instance, $page, 1); } // Create a group. $group = new stdClass(); $group->name = 'testgroup'; $group->courseid = $courseid; $groupid = groups_create_group($group); // Add the user to the group. groups_add_member($groupid, $userid); // Create a grouping containing our group. $grouping = new stdClass(); $grouping->name = 'testgrouping'; $grouping->courseid = $courseid; $groupingid = groups_create_grouping($grouping); groups_assign_grouping($groupingid, $groupid); // Set up a user tag. tag_set('course', $courseid, array('testtag')); // Add a course-level log. add_to_log($courseid, 'bogus', 'bogus'); // Set up the default course question category. $newcategory = question_make_default_categories(array($coursecontext)); // Create a test question. $question = new stdClass(); $question->qtype = 'truefalse'; $form = new stdClass(); $form->category = $newcategory->id; $form->name = 'testquestion'; $form->correctanswer = 1; $form->feedbacktrue = array('text' => 'bogustext', 'format' => FORMAT_HTML); $form->feedbackfalse = array('text' => 'bogustext', 'format' => FORMAT_HTML); $question = question_bank::get_qtype('truefalse')->save_question($question, $form); if (function_exists('course_set_display')) { // Set a "course display" setting. course_set_display($courseid, 1); } // Make a bogus backup record. $backupcourse = new stdClass(); $backupcourse->courseid = $courseid; $DB->insert_record('backup_courses', $backupcourse); // Add a user lastaccess record. $lastaccess = new stdClass(); $lastaccess->userid = $userid; $lastaccess->courseid = $courseid; $DB->insert_record('user_lastaccess', $lastaccess); // Make a bogus backup log record. $log = new stdClass(); $log->backupid = $courseid; $log->timecreated = time(); $log->loglevel = 1; $log->message = 'bogus'; $DB->insert_record('backup_logs', $log); // Get initial counts. $initialnumcourse = $DB->count_records('course'); $initialnumroleassignments = $DB->count_records('role_assignments'); $initialnumuserenrolments = $DB->count_records('user_enrolments'); $initialnumgradeitems = $DB->count_records('grade_items'); $initialnumgradegrades = $DB->count_records('grade_grades'); $initialnumgradeoutcomes = $DB->count_records('grade_outcomes'); $initialnumgradeoutcomescourses = $DB->count_records('grade_outcomes_courses'); $initialnumscale = $DB->count_records('scale'); $initialnumgradesettings = $DB->count_records('grade_settings'); $initialnumgradeletters = $DB->count_records('grade_letters'); $initialnumforum = $DB->count_records('forum'); $initialnumcoursemodules = $DB->count_records('course_modules'); $initialnumcoursemodulescompletion = $DB->count_records('course_modules_completion'); $initialnumcoursemodulesavailability = $DB->count_records('course_modules_availability'); $initialnumblockinstances = $DB->count_records('block_instances'); $initialnumblockpositions = $DB->count_records('block_positions'); $initialnumgroups = $DB->count_records('groups'); $initialnumgroupsmembers = $DB->count_records('groups_members'); $initialnumgroupings = $DB->count_records('groupings'); $initialnumgroupingsgroups = $DB->count_records('groupings_groups'); $initialnumtaginstance = $DB->count_records('tag_instance'); $initialnumcoursesections = $DB->count_records('course_sections'); $initialnumquestioncategories = $DB->count_records('question_categories'); $initialnumquestion = $DB->count_records('question'); if (self::$coursedisplay) { $initialnumcoursedisplay = $DB->count_records('course_display'); } $initialnumbackupcourses = $DB->count_records('backup_courses'); $initialnumuserlastaccess = $DB->count_records('user_lastaccess'); $initialnumbackuplogs = $DB->count_records('backup_logs'); // Delete the course. $data = array('action' => 'delete', 'shortname' => 'deleteassociationsshortname'); $this->run_core_course_import($data, false); // Validate the result. $this->assertEquals($DB->count_records('course'), $initialnumcourse - 1); $this->assertEquals($DB->count_records('role_assignments'), $initialnumroleassignments - 1); $this->assertEquals($DB->count_records('user_enrolments'), $initialnumuserenrolments - 1); $this->assertEquals($DB->count_records('grade_items'), $initialnumgradeitems - 2); $this->assertEquals($DB->count_records('grade_grades'), $initialnumgradegrades - 1); $this->assertEquals($DB->count_records('grade_outcomes'), $initialnumgradeoutcomes - 1); $this->assertEquals($DB->count_records('grade_outcomes_courses'), $initialnumgradeoutcomescourses - 1); $this->assertEquals($DB->count_records('scale'), $initialnumscale - 1); $this->assertEquals($DB->count_records('grade_settings'), $initialnumgradesettings - 1); $this->assertEquals($DB->count_records('grade_letters'), $initialnumgradeletters - 1); $this->assertEquals($DB->count_records('forum'), $initialnumforum - 1); $this->assertEquals($DB->count_records('course_modules'), $initialnumcoursemodules - 1); /* Uncomment the two lines below when this fix is available: http://tracker.moodle.org/browse/MDL-32988 $this->assertEquals($DB->count_records('course_modules_completion'), $initialnumcourse_modules_completion - 1); $this->assertEquals($DB->count_records('course_modules_availability'), $initialnumcourse_modules_availability - 1); */ $this->assertEquals($initialnumblockinstances - 4, $DB->count_records('block_instances')); $this->assertEquals($DB->count_records('block_positions'), 0); $this->assertEquals($DB->count_records('groups'), $initialnumgroups - 1); $this->assertEquals($DB->count_records('groups_members'), $initialnumgroupsmembers - 1); $this->assertEquals($DB->count_records('groupings'), $initialnumgroupings - 1); $this->assertEquals($DB->count_records('groupings_groups'), $initialnumgroupingsgroups - 1); $this->assertEquals($DB->count_records('log', array('course' => $courseid)), 0); $this->assertEquals($DB->count_records('tag_instance'), $initialnumtaginstance - 1); $this->assertEquals($DB->count_records('course_sections'), $initialnumcoursesections - 1); $this->assertEquals($DB->count_records('question_categories'), $initialnumquestioncategories - 1); $this->assertEquals($DB->count_records('question'), $initialnumquestion - 1); if (self::$coursedisplay) { $this->assertEquals($DB->count_records('course_display'), $initialnumcoursedisplay - 1); } $this->assertEquals($DB->count_records('backup_courses'), $initialnumbackupcourses - 1); $this->assertEquals($DB->count_records('user_lastaccess'), $initialnumuserlastaccess - 1); }