moodle-block_mytreestudyplan/classes/levelset.php
pmk 6b16559201 Added web service for managing levelsets (now mostly called "skills")
Default levels can be configured.
Working on management interface for predefined skills.
2018-09-21 22:30:09 +02:00

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);
}
}