/** * Using this object's id field, fetches the matching record in the DB, and looks at * each variable in turn. If the DB has different data, the db's data is used to update * the object. This is different from the update() function, which acts on the DB record * based on the object. */ public function update_from_db() { if (empty($this->id)) { debugging("The object could not be used in its state to retrieve a matching record from the DB, because its id field is not set."); return false; } global $DB; if (!($params = $DB->get_record($this->table, array('id' => $this->id)))) { debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!"); return false; } grade_object::set_properties($this, $params); return true; }
/** * Static method - returns all outcomes available in course * @static * @param int $courseid * @return array */ function fetch_all_available($courseid) { global $CFG; $result = array(); $sql = "SELECT go.*\n FROM {$CFG->prefix}grade_outcomes go, {$CFG->prefix}grade_outcomes_courses goc\n WHERE go.id = goc.outcomeid AND goc.courseid = {$courseid}\n ORDER BY go.id ASC"; if ($datas = get_records_sql($sql)) { foreach ($datas as $data) { $instance = new grade_outcome(); grade_object::set_properties($instance, $data); $result[$instance->id] = $instance; } } return $result; }
/** * Overrides grade_object::set_properties() to add special handling for changes to category aggregation types * * @param stdClass $instance the object to set the properties on * @param array|stdClass $params Either an associative array or an object containing property name, property value pairs */ public static function set_properties(&$instance, $params) { global $DB; parent::set_properties($instance, $params); //if they've changed aggregation type we made need to do some fiddling to provide appropriate defaults if (!empty($params->aggregation)) { //weight and extra credit share a column :( Would like a default of 1 for weight and 0 for extra credit //Flip from the default of 0 to 1 (or vice versa) if ALL items in the category are still set to the old default. if ($params->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN || $params->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { $sql = $defaultaggregationcoef = null; if ($params->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { //if all items in this category have aggregation coefficient of 0 we can change it to 1 ie evenly weighted $sql = "select count(id) from {grade_items} where categoryid=:categoryid and aggregationcoef!=0"; $defaultaggregationcoef = 1; } else { if ($params->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { //if all items in this category have aggregation coefficient of 1 we can change it to 0 ie no extra credit $sql = "select count(id) from {grade_items} where categoryid=:categoryid and aggregationcoef!=1"; $defaultaggregationcoef = 0; } } $params = array('categoryid' => $instance->id); $count = $DB->count_records_sql($sql, $params); if ($count === 0) { //category is either empty or all items are set to a default value so we can switch defaults $params['aggregationcoef'] = $defaultaggregationcoef; $DB->execute("update {grade_items} set aggregationcoef=:aggregationcoef where categoryid=:categoryid", $params); } } } }
/** * Static method that returns all outcomes available in course * * @static * @param int $courseid * @return array */ public static function fetch_all_available($courseid) { global $CFG, $DB; $result = array(); $params = array($courseid); $sql = "SELECT go.*\n FROM {grade_outcomes} go, {grade_outcomes_courses} goc\n WHERE go.id = goc.outcomeid AND goc.courseid = ?\n ORDER BY go.id ASC"; if ($datas = $DB->get_records_sql($sql, $params)) { foreach ($datas as $data) { $instance = new grade_outcome(); grade_object::set_properties($instance, $data); $result[$instance->id] = $instance; } } return $result; }
/** * Obtains the name of a grade item. * * @global object * @param object $gradeitemobj Object from get_record on grade_items table, * (can be empty if you want to just get !missing) * @return string Name of item of !missing if it didn't exist */ private static function get_grade_name($gradeitemobj) { global $CFG; if (isset($gradeitemobj->id)) { require_once $CFG->libdir . '/gradelib.php'; $item = new grade_item(); grade_object::set_properties($item, $gradeitemobj); return $item->get_name(); } else { return '!missing'; // Ooops, missing grade } }
/** * Obtains the name of a grade item, also checking that it exists. Uses a * cache. The name returned is suitable for display. * * @param int $courseid Course id * @param int $gradeitemid Grade item id * @return string Grade name or empty string if no grade with that id */ private static function get_cached_grade_name($courseid, $gradeitemid) { global $DB, $CFG; require_once $CFG->libdir . '/gradelib.php'; // Get all grade item names from cache, or using db query. $cache = \cache::make('availability_grade', 'items'); if (($cacheditems = $cache->get($courseid)) === false) { // We cache the whole items table not the name; the format_string // call for the name might depend on current user (e.g. multilang) // and this is a shared cache. $cacheditems = $DB->get_records('grade_items', array('courseid' => $courseid)); $cache->set($courseid, $cacheditems); } // Return name from cached item or a lang string. if (!array_key_exists($gradeitemid, $cacheditems)) { return get_string('missing', 'availability_grade'); } $gradeitemobj = $cacheditems[$gradeitemid]; $item = new \grade_item(); \grade_object::set_properties($item, $gradeitemobj); return $item->get_name(); }
/** * Overrides grade_object::set_properties() to add special handling for changes to category aggregation types * * @param stdClass $instance the object to set the properties on * @param array|stdClass $params Either an associative array or an object containing property name, property value pairs */ public static function set_properties(&$instance, $params) { global $DB; $fromaggregation = $instance->aggregation; parent::set_properties($instance, $params); // The aggregation method is changing and this category has already been saved. if (isset($params->aggregation) && !empty($instance->id)) { $achildwasdupdated = false; // Get all its children. $children = $instance->get_children(); foreach ($children as $child) { $item = $child['object']; if ($child['type'] == 'category') { $item = $item->load_grade_item(); } // Set the new aggregation fields. if ($item->set_aggregation_fields_for_aggregation($fromaggregation, $params->aggregation)) { $item->update(); $achildwasdupdated = true; } } // If this is the course category, it is possible that its grade item was set as needsupdate // by one of its children. If we keep a reference to that stale object we might cause the // needsupdate flag to be lost. It's safer to just reload the grade_item from the database. if ($achildwasdupdated && !empty($instance->grade_item) && $instance->is_course_category()) { $instance->grade_item = null; $instance->load_grade_item(); } } }
/** * Tests the is_available and get_description functions. */ public function test_usage() { global $CFG, $DB; require_once $CFG->dirroot . '/mod/assign/locallib.php'; $this->resetAfterTest(); // Create course with completion turned on. $CFG->enablecompletion = true; $CFG->enableavailability = true; $generator = $this->getDataGenerator(); $course = $generator->create_course(array('enablecompletion' => 1)); $user = $generator->create_user(); $generator->enrol_user($user->id, $course->id); $this->setUser($user); // Create a Page with manual completion for basic checks. $page = $generator->get_plugin_generator('mod_page')->create_instance(array('course' => $course->id, 'name' => 'Page!', 'completion' => COMPLETION_TRACKING_MANUAL)); // Create an assignment - we need to have something that can be graded // so as to test the PASS/FAIL states. Set it up to be completed based // on its grade item. $assignrow = $this->getDataGenerator()->create_module('assign', array('course' => $course->id, 'name' => 'Assign!', 'completion' => COMPLETION_TRACKING_AUTOMATIC)); $DB->set_field('course_modules', 'completiongradeitemnumber', 0, array('id' => $assignrow->cmid)); $assign = new assign(context_module::instance($assignrow->cmid), false, false); // Get basic details. $modinfo = get_fast_modinfo($course); $pagecm = $modinfo->get_cm($page->cmid); $assigncm = $assign->get_course_module(); $info = new \core_availability\mock_info($course, $user->id); // COMPLETE state (false), positive and NOT. $cond = new condition((object) array('cm' => (int) $pagecm->id, 'e' => COMPLETION_COMPLETE)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Page!.*is marked complete~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // INCOMPLETE state (true). $cond = new condition((object) array('cm' => (int) $pagecm->id, 'e' => COMPLETION_INCOMPLETE)); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, true, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Page!.*is marked complete~', $information); // Mark page complete. $completion = new completion_info($course); $completion->update_state($pagecm, COMPLETION_COMPLETE); // COMPLETE state (true). $cond = new condition((object) array('cm' => (int) $pagecm->id, 'e' => COMPLETION_COMPLETE)); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, true, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Page!.*is incomplete~', $information); // INCOMPLETE state (false). $cond = new condition((object) array('cm' => (int) $pagecm->id, 'e' => COMPLETION_INCOMPLETE)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Page!.*is incomplete~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // We are going to need the grade item so that we can get pass/fails. $gradeitem = $assign->get_grade_item(); grade_object::set_properties($gradeitem, array('gradepass' => 50.0)); $gradeitem->update(); // With no grade, it should return true for INCOMPLETE and false for // the other three. $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_INCOMPLETE)); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // Check $information for COMPLETE_PASS and _FAIL as we haven't yet. $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE_PASS)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Assign!.*is complete and passed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Assign!.*is complete and failed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // Change the grade to be complete and failed. self::set_grade($assignrow, $user->id, 40); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_INCOMPLETE)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE)); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE_PASS)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Assign!.*is complete and passed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL)); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, true, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Assign!.*is not complete and failed~', $information); // Now change it to pass. self::set_grade($assignrow, $user->id, 60); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_INCOMPLETE)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE)); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE_PASS)); $this->assertTrue($cond->is_available(false, $info, true, $user->id)); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $information = $cond->get_description(false, true, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Assign!.*is not complete and passed~', $information); $cond = new condition((object) array('cm' => (int) $assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~Assign!.*is complete and failed~', $information); $this->assertTrue($cond->is_available(true, $info, true, $user->id)); // Simulate deletion of an activity by using an invalid cmid. These // conditions always fail, regardless of NOT flag or INCOMPLETE. $cond = new condition((object) array('cm' => $assigncm->id + 100, 'e' => COMPLETION_COMPLETE)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); $information = $cond->get_description(false, false, $info); $information = \core_availability\info::format_info($information, $course); $this->assertRegExp('~(Missing activity).*is marked complete~', $information); $this->assertFalse($cond->is_available(true, $info, true, $user->id)); $cond = new condition((object) array('cm' => $assigncm->id + 100, 'e' => COMPLETION_INCOMPLETE)); $this->assertFalse($cond->is_available(false, $info, true, $user->id)); }
public function test_course_completion_progress() { global $CFG, $DB; $CFG->enablecompletion = true; $DB->update_record('course', (object) ['id' => $this->course->id, 'enablecompletion' => 1]); $this->course = $DB->get_record('course', ['id' => $this->course->id]); $actual = local::course_completion_progress($this->course); $this->assertNull($actual); $this->create_extra_users(); $this->setUser($this->extrasuspendedstudents[0]); $actual = local::course_completion_progress($this->course); $this->assertNull($actual); $this->setUser($this->students[0]); $actual = local::course_completion_progress($this->course); $this->assertNull($actual); // Create an assignment that is enabled for completion. $this->setUSer($this->teachers[0]); $assign = $this->create_instance(['course' => $this->course->id, 'name' => 'Assign!', 'completion' => COMPLETION_TRACKING_AUTOMATIC]); // Should now have something that can be tracked for progress. $this->setUser($this->students[0]); $actual = local::course_completion_progress($this->course); $this->assertInstanceOf('stdClass', $actual); $this->assertEquals(0, $actual->complete); $this->assertEquals(1, $actual->total); $this->assertEquals(0, $actual->progress); // Make sure completion updates on grading. $DB->set_field('course_modules', 'completiongradeitemnumber', 0, ['id' => $assign->get_course_module()->id]); $gradeitem = $assign->get_grade_item(); grade_object::set_properties($gradeitem, array('gradepass' => 50.0)); $gradeitem->update(); $assignrow = $assign->get_instance(); $grades = array(); $grades[$this->students[0]->id] = (object) ['rawgrade' => 60, 'userid' => $this->students[0]->id]; $assignrow->cmidnumber = null; assign_grade_item_update($assignrow, $grades); $actual = local::course_completion_progress($this->course); $this->assertInstanceOf('stdClass', $actual); $this->assertEquals(1, $actual->complete); $this->assertEquals(1, $actual->total); $this->assertEquals(100, $actual->progress); // Make sure course completion returns null when disabled at site level. $CFG->enablecompletion = false; $actual = local::course_completion_progress($this->course); $this->assertNull($actual); // Make sure course completion returns null when disabled at course level. $CFG->enablecompletion = true; $DB->update_record('course', (object) ['id' => $this->course->id, 'enablecompletion' => 0]); $this->course = $DB->get_record('course', ['id' => $this->course->id]); $actual = local::course_completion_progress($this->course); $this->assertNull($actual); }
/** * Returns the grade items associated with the courses. * * @param array $courseids Array of course ids. * @return array Array of grade_items for each course level grade item. */ public static function fetch_course_items(array $courseids) { global $DB; $courseitems = array(); if (!empty($courseids)) { list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($courseids); $select = 'courseid ' . $courseidssql . ' AND itemtype = ?'; $params = array_merge($courseidsparams, array('course')); $rs = $DB->get_recordset_select('grade_items', $select, $params); $courseitems = array(); foreach ($rs as $data) { $instance = new \grade_item(); \grade_object::set_properties($instance, $data); $courseitems[$instance->id] = $instance; } $rs->close(); } // Determine if any courses were missing. $receivedids = array(); foreach ($courseitems as $item) { $receivedids[] = $item->courseid; } $missingids = array_diff($courseids, $receivedids); // Create grade items for any courses that didn't have one. if (!empty($missingids)) { foreach ($missingids as $courseid) { // First get category - it creates the associated grade item. $coursecategory = \grade_category::fetch_course_category($courseid); $gradeitem = $coursecategory->get_grade_item(); $courseitems[$gradeitem->id] = $gradeitem; } } return $courseitems; }