6b16559201
Default levels can be configured. Working on management interface for predefined skills.
494 lines
12 KiB
PHP
494 lines
12 KiB
PHP
<?php
|
|
require_once($CFG->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($id)
|
|
{
|
|
$levelset = $DB->get_record('block_gradelevel_levelset', array('id' => array_values($records)[0]->levelset_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))
|
|
{
|
|
print ("EXSTING_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;
|
|
}
|
|
elseif(isset($this->global_levels[$i]))
|
|
{
|
|
$color = $this->global_levels[$i]->badgecolor;
|
|
}
|
|
else
|
|
{
|
|
$color = static::UNDEFINED_BADGE_COLOR;
|
|
}
|
|
|
|
$level_info[$lvl->points];
|
|
}
|
|
|
|
}
|
|
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: ",6) != 0)
|
|
{
|
|
$image_url = $CFG->wwwroot.$image;
|
|
}
|
|
else
|
|
{
|
|
$image_url = $image;
|
|
}
|
|
|
|
$html = "<figure class='levelbadge'";
|
|
$html .= " data-color='{$info->badge_color}'";
|
|
$html .= " data-progress='{$info->progress}'";
|
|
$html .= " data-level='{$info->level}'";
|
|
$html .= " data-width='{$size}'";
|
|
$html .= " data-height='{$size}'";
|
|
$html .= ">";
|
|
if(!empty($image))
|
|
{
|
|
$html .= "<img style='display:none' src='{$image_url}' />";
|
|
}
|
|
$html .= "</figure>";
|
|
return $html;
|
|
}
|
|
|
|
public function render_demo_badge(int $size=100)
|
|
{
|
|
$levels = $this->badgelevels();
|
|
$maxpoints = array_pop(array_keys($levels));
|
|
|
|
return $this->render_badge($maxpoints,$size);
|
|
|
|
}
|
|
|
|
} |