libdir.'/gradelib.php'); require_once($CFG->dirroot.'/grade/querylib.php'); //namespace block_gradelevel; class block_gradelevel_levelset { const UNDEFINED_BADGE_COLOR="#3F3F3F"; const NULL_BADGE_COLOR = "#000000"; const DEFAULT_ICON = "/blocks/gradelevel/pix/undefinedskill.svg"; const GLOBAL_DEFAULTS = array( 0 => "#000000", 250 => "#2ad4ff", // + 250 750 => "#cd7f32", // + 500 1750 => "#a6a6a6", // + 1000 2750 => "#f6ae00", // + 2000 ); private $id; private $data; private $levels = null; private $global_levels = null; /** * Construct a levelset object for an existing database item * */ private function __construct($id = null, $dataObject = null) { global $DB; $this->id = $id; if($id != null) { if(isset($dataObject) && isset($dataObject->id) && $dataObject->id == $id) // slight sanity check { $this->data = $dataObject; } else { // database validity check went south, retrieve again $this->data = $DB->get_record('block_gradelevel_levelset', array('id' => $this->id)); } //retrieve levels for this levelset $this->levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => $this->id)); usort( $this->levels, function( $a, $b) { return ( $a->points < $b->points ) ? -1 : 1; } ); } // retrieve global levels $this->global_levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); usort( $this->global_levels, function( $a, $b) { return ( $a->points < $b->points ) ? -1 : 1; } ); // if no global levels are defined, insert default global levels if(count($this->global_levels) == 0) { foreach(static::GLOBAL_DEFAULTS as $points => $color) { // setup default $row = new stdClass; $row->levelset_id = 0; $row->points = $points; $row->badgecolor = $color; // insert into db if(!$DB->insert_record('block_gradelevel_levels',$row)){ print_error('inserterror', 'block_gradelevel'); } } // and reload global levels; $this->global_levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); usort( $this->global_levels, function( $a, $b) { return ( $a->points < $b->points ) ? -1 : 1; } ); } } public function getId() : string { return $this->id; } public function setName(string $name) { $this->data->name = $name; } public function getName() : string { return $this->data->name; } public function setIcon(string $iconname) { $this->data->icon = $iconname; } public function getIcon() : string { $icon = $this->data->icon; if(empty($icon)) { $icon = static::DEFAULT_ICON; } return $icon; } /** * Find a levelset for a given course * * @params int $course_id The id of the course to find a levelset for * @return levelset The levelset for this course or null if none found; */ static public function find_by_course($course_id) { global $DB; // FIXME: Make this more efficient by joining it into one sql statement. $records = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id)); if(count($records) > 0) { $levelset = $DB->get_record('block_gradelevel_levelset', array('id' => array_values($records)[0]->levelset_id)); if($levelset) { return new static($levelset->id,$levelset); } } return null; // return null if no current levelset linked } static public function find_by_id(int $id) { global $DB; $levelset = $DB->get_record('block_gradelevel_levelset', array('id' => $id)); if($levelset) { return new static($levelset->id,$levelset); } else { return null; } } /** * List attached courses for this levelset * * @return array An array with the id's of attached courses */ public function list_courses() { global $DB; $list = array(); $links = $DB->get_records('block_gradelevel_course_link', array('levelset_id' => $this->id)); foreach($links as $link) { $list[] = $link->course_id; } return $list; } /** * Attach a course to this levelset. The course will be detached from any other levelsets. * * @params int $course_id The id of the course to attach */ public function attach_course($course_id) { global $DB; // check if course attachement is already done if(!in_array($course_id,$this->list_courses)) { // no, now find an existing attachment for this course $rows = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id)); if(empty($rows)) { // create new attachment if existing link was not found $row = new stdClass; $row->levelset_id = $this->id; $row->course_id = $course_id; // insert new row if(!$DB->insert_record('block_gradelevel_course_link',$row)){ print_error('inserterror', 'block_gradelevel'); } } else { // update existing link (automatically detaches course from its previous levelset) $row = array_values($rows)[0]; $row->course_id = $course_id; // update existing row if(!$DB->update_record('block_gradelevel_course_link',$row)){ print_error('updateerror', 'block_gradelevel'); } } } } /** * Detache a course from this levelset. * * @params int $course_id The id of the course to detach */ public function detach_course($course_id) { global $DB; $rows = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id, 'levelset_id' => $this->id)); if(!empty($rows)) { if(!$DB->delete_records('block_gradelevel_course_link', array('id' => array_values($rows)[0]->id))) { print_error('deleteerror','block_gradelevel'); } } } /** * Store changes made to the levelset data parameter containing levelset data * */ public function save_data() { global $DB; if($this->data->id == $this->id) // sanity check so we don't kill everything { if(!$DB->update_record('block_gradelevel_levelset',$this->data)){ print_error('updateerror', 'block_gradelevel'); error_log('UPDATE-ERROR\n', 3, '/tmp/moodledevlog.log'); } } else { print_error('datarowchanged_error', 'block_gradelevel'); error_log('DATAROW-INVALID\n', 3, '/tmp/moodledevlog.log'); } } /** * Retrieve point total for all attached courses for a given user * * @param int $user_id The id * @return int Total points for this user in this levelset */ public function get_levelset_grade($user_id) { // loop through all attached courses and add up the grade points gathered $points = 0; foreach($this->list_courses() as $course_id) { $result = grade_get_course_grade($user_id,$course_id); $points += $result->grade; } return $points; } /** * Return the levelup data for a given userid in this levelset * * @param int $user_id The id * @return stdClass A stdClass containing the level data for the specified number of point */ public function get_user_leveldata($user_id) { $points = $this->get_levelset_grade($user_id); return $this->calculate_level($points); } /** * Create a new levelset * * @params string $name Optional name of the new levelset * @return levelset The new levelset */ static public function create_new($name="New levelset") { global $DB; // create a new levelset $row = new stdClass; $row->name = $name; if(!$id = $DB->insert_record('block_gradelevel_levelset',$row, true)){ print_error('inserterror', 'block_gradelevel'); } else { $rows = $DB->get_records('block_gradelevel_levelset', array('id' => $id)); if(count($rows) > 0) { return new static($id,array_values($rows)[0]); } } throw new RuntimeException("Could not create new levelset"); } /** * List all levelsets * * @return array Array of levelset */ static public function list_all() { global $DB; $list = array(); $levelsets = $DB->get_records('block_gradelevel_levelset'); foreach($levelsets as $lset) { $list[] = new static($lset->id,$lset); } return $list; } /** * Calculate the levelup data, given a specified set of points * * @params int points The amount of points to calculate for * @return stdClass A stdClass containing the level data for the specified number of point */ public function calculate_level($points){ $levels = $this->badgelevels($levelset_id); $level = 0; $badge_color = static::NULL_BADGE_COLOR; $current_at = 0; $next_at = 0; foreach($levels as $threshold => $badgeColor) { if($points >= $threshold){ $level++; $badge_color = $badgeColor; $current_at = $threshold; } else { $next_at = $threshold; break; } } $levelup_points = $next_at - $current_at; $points_in_level = $points - $current_at; if($levelup_points == 0){ // at max level $progress = 0; $points_in_level = 0; } else { $progress = $points_in_level / $levelup_points; } $result = new stdClass; $result->level = $level; $result->badge_color = $badge_color; $result->progress = $progress; $result->next_at = $next_at; $result->levelup_total = $levelup_points; $result->points_in_level = $points_in_level; return $result; } /** * Simplified list of levels and associated badge colors for this levelset * Takes data from global levelset if more specialized data is not set * * @return array An array of points (keys) and badge color (values), sorted by level */ public function badgelevels() { $level_info = array(); // If we have levels defined, use those, otherwise use the global levels if(!empty($this->levels)) { if(array_values($this->levels)[0]->points > 0) { // insert level 0 $level_info[0] = static::NULL_BADGE_COLOR; } $i = 0; foreach($this->levels as $lvl) { // Check if color is properly set or needs to be retrieved from global config if(!empty($lvl->badgecolor)) { $color = $lvl->badgecolor; } else { $color = static::UNDEFINED_BADGE_COLOR; } $level_info[$lvl->points] = $color; $i++; } } else { if(empty($this->global_levels) || array_values($this->global_levels)[0]->points > 0) { // insert level 1 if levels don't start at 0 points, // or if no global levels are defined. - At least start somewhere... $level_info[0] = static::NULL_BADGE_COLOR; } // use global levels if levelset is not defined. foreach($this->global_levels as $lvl) { // Check if color is properly set if(!empty($lvl->badgecolor)) { $color = $lvl->badgecolor; } else { $color = static::UNDEFINED_BADGE_COLOR; } $level_info[$lvl->points] = $color; } } return $level_info; } public function render_badge(int $points,int $size=150){ global $CFG; $info = $this->calculate_level($points); $image = $this->getIcon(); if(strncmp($image,"data:",5) != 0) { $image_url = $CFG->wwwroot.$image; } else { $image_url = $image; } $html = "
badgelevels(); $maxpoints = array_pop(array_keys($levels)); return $this->render_badge($maxpoints, $size); } }