Commit of working grade level plugin

Todo for first usable version: Add level customization per levelset (course)
This commit is contained in:
pmk 2018-09-17 13:12:56 +02:00
commit d70463cfba
16 changed files with 2035 additions and 0 deletions

212
amd/src/renderbadge.js Normal file
View File

@ -0,0 +1,212 @@
// Put this file in path/to/plugin/amd/src
// You can call it anything you like
define(['jquery', 'core/str', 'core/ajax'], function ($, str, ajax) {
let self = {
init: function init() {
// Put whatever you like here. $ is available
// to you as normal.
$("figure.levelbadge").each(function () {
let props = self.fetchProperties(this);
let $this = $(this);
let $canvas = $("<canvas height= '"+props.height +"' width = '"+props.width+"'/>");
console.info("$canvas", $canvas, $canvas[0]);
$('canvas', this).remove();// clear out any existing canvas
$this.append($canvas); // Put the canvas in there
self.render($canvas[0], props);
});
},
render: function render(canvas, props) {
let ctx = canvas.getContext("2d");
// Color configuration
let colors = {
base: props.color,
light: self.shadeBlendConvert(0.6, props.color), // 60% lighter
dark: self.shadeBlendConvert(0.3, props.color), // 30% lighter
lightPoint: self.shadeBlendConvert(0.8, props.color), // 80% lighter
reflection: {
lightest: "#ffffff51",
darkest: "#ffffff20",
},
radialGradient: {
x0: 0.75,
y0: 0.25,
r0: 0.05,
x1: 0.6,
y1: 0.4,
r1: 0.4,
},
levelText: "white",
};
// size and position configuration
let config = {
size: Math.min(props.height, props.width),
borderWidth: 0.05, // factor of image size
reflection: {
angle: -20, // relative to horizontal
offset: 0.125, // relative to radius
},
levelText: {
x: 0.5,
y: 0.9,
size: 0.2,
font: "Open Sans, Arial, helvetica, sans-serif",
},
icon: {
x: 0.5,
y: 0.47,
scale: 0.7, // scale to this fraction of image size
}
}
console.info("Config", config);
console.info("Colors", colors);
console.info("Props", props);
// draw main circle
let baseGradient = ctx.createRadialGradient(
config.size * colors.radialGradient.x0,
config.size * colors.radialGradient.y0,
(config.size ) * colors.radialGradient.r0,
config.size * colors.radialGradient.x1,
config.size * colors.radialGradient.y1,
(config.size ) * colors.radialGradient.r1,
);
baseGradient.addColorStop(0, colors.lightPoint);
baseGradient.addColorStop(1, colors.base);
ctx.beginPath();
ctx.fillStyle = baseGradient;
ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2, 0, 2 * Math.PI);
ctx.fill();
// draw main reflection
let rflOffset = Math.asin(config.reflection.offset);
let rflAngleRad = (config.reflection.angle / 360.0) * 2 * Math.PI;
let rflGradient = ctx.createLinearGradient(
(0.5 - config.reflection.offset * Math.sin(rflAngleRad))/2 * config.size,
(0.5 + config.reflection.offset * Math.cos(rflAngleRad))/2 * config.size,
Math.sin(rflAngleRad)/2 * config.size,
Math.cos(rflAngleRad)/2 * config.size
);
rflGradient.addColorStop(0, colors.reflection.lightest);
rflGradient.addColorStop(1, colors.reflection.darkest);
ctx.beginPath();
ctx.fillStyle = rflGradient;
ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2,
0 + rflOffset + rflAngleRad,
Math.PI - rflOffset + rflAngleRad);
ctx.fill();
// draw empty border
let strokeWidth = config.size * config.borderWidth;
ctx.beginPath();
ctx.strokeStyle = colors.light;
ctx.lineWidth = strokeWidth;
ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2 - strokeWidth / 2, 0, 2 * Math.PI);
ctx.stroke();
// draw current progress border
ctx.beginPath();
ctx.strokeStyle = colors.dark;
ctx.lineWidth = strokeWidth;
ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2 - strokeWidth / 2,
0 - Math.PI / 2, // -90 degrees (top)
props.progress * 2 * Math.PI - Math.PI / 2 // fraction of whole circle offset with -90 degrees
);
ctx.stroke();
if (props.level) {
// write level in lower part
ctx.font = "" + config.size * config.levelText.size + "px " + config.levelText.font;
ctx.fillStyle = colors.levelText;
ctx.textAlign = "center";
ctx.fillText("" + props.level, config.size * config.levelText.x, config.size * config.levelText.y);
}
/*
var imageObj = new Image();
imageObj.onload = function () {
ctx.drawImage(this, 15, 15, 120, 120);
};
imageObj.src = "https://dev.miqra.nl/blocks/gradelevel/pix/undefinedskill.svg";
*/
// paint image in center
if (props.image) {
let iconImg = new Image();
iconImg.onload = function () {
let imPos = {
x: 0, // to be determined later
y: 0, // to be determined later
w: config.size * config.icon.scale,
h: config.size * config.icon.scale,
};
// preserve aspect ratio
if (this.width > this.height) {
imPos.h *= this.height / this.width;
} else {
imPos.w *= this.width / this.height;
}
// calculate x and y
imPos.x = (config.size * config.icon.x) - (imPos.w / 2);
imPos.y = (config.size * config.icon.y) - (imPos.h / 2);
ctx.drawImage(this, 15, 15, 120, 120);//imPos.x, imPos.y, imPos.w, imPos.h);
}
console.info("Image: ",props.image);
iconImg.src = props.image;
}
// complete
},
fetchProperties: function fetchProperties(figure) {
let $figure = $(figure);
let $image = $figure.find("img");
let image = null;
if ($image.length > 0) {
image = $image.attr("src");
}
return {
progress: $figure.attr("data-progress"),
width: $figure.attr("data-width"),
height: $figure.attr("data-height"),
color: $figure.attr("data-color"),
level: $figure.attr("data-level"),
image: image,
};
},
shadeBlendConvert: function shadeBlendConvert(p, from, to) {
// Code for this function was taken from https://github.com/PimpTrizkit/PJs pSBC.js
if (typeof (p) != "number" || p < -1 || p > 1 || typeof (from) != "string" || (from[0] != 'r' && from[0] != '#') || (to && typeof (to) != "string")) return null; //ErrorCheck
if (!this.sbcRip) this.sbcRip = (d) => {
let l = d.length, RGB = {};
if (l > 9) {
d = d.split(",");
if (d.length < 3 || d.length > 4) return null;//ErrorCheck
RGB[0] = i(d[0].split("(")[1]), RGB[1] = i(d[1]), RGB[2] = i(d[2]), RGB[3] = d[3] ? parseFloat(d[3]) : -1;
} else {
if (l == 8 || l == 6 || l < 4) return null; //ErrorCheck
if (l < 6) d = "#" + d[1] + d[1] + d[2] + d[2] + d[3] + d[3] + (l > 4 ? d[4] + "" + d[4] : ""); //3 or 4 digit
d = i(d.slice(1), 16), RGB[0] = d >> 16 & 255, RGB[1] = d >> 8 & 255, RGB[2] = d & 255, RGB[3] = -1;
if (l == 9 || l == 5) RGB[3] = r((RGB[2] / 255) * 10000) / 10000, RGB[2] = RGB[1], RGB[1] = RGB[0], RGB[0] = d >> 24 & 255;
}
return RGB;
}
var i = parseInt, r = Math.round, h = from.length > 9, h = typeof (to) == "string" ? to.length > 9 ? true : to == "c" ? !h : false : h, b = p < 0, p = b ? p * -1 : p, to = to && to != "c" ? to : b ? "#000000" : "#FFFFFF", f = this.sbcRip(from), t = this.sbcRip(to);
if (!f || !t) return null; //ErrorCheck
if (h) return "rgb" + (f[3] > -1 || t[3] > -1 ? "a(" : "(") + r((t[0] - f[0]) * p + f[0]) + "," + r((t[1] - f[1]) * p + f[1]) + "," + r((t[2] - f[2]) * p + f[2]) + (f[3] < 0 && t[3] < 0 ? ")" : "," + (f[3] > -1 && t[3] > -1 ? r(((t[3] - f[3]) * p + f[3]) * 10000) / 10000 : t[3] < 0 ? f[3] : t[3]) + ")");
else return "#" + (0x100000000 + r((t[0] - f[0]) * p + f[0]) * 0x1000000 + r((t[1] - f[1]) * p + f[1]) * 0x10000 + r((t[2] - f[2]) * p + f[2]) * 0x100 + (f[3] > -1 && t[3] > -1 ? r(((t[3] - f[3]) * p + f[3]) * 255) : t[3] > -1 ? r(t[3] * 255) : f[3] > -1 ? r(f[3] * 255) : 255)).toString(16).slice(1, f[3] > -1 || t[3] > -1 ? undefined : -2);
},
};
return self;
});

103
block_gradelevel.php Normal file
View File

@ -0,0 +1,103 @@
<?php
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/grade/querylib.php');
require_once($CFG->dirroot.'/blocks/gradelevel/lib.php');
use block_gradelevel;
class block_gradelevel extends block_base {
public $levelset;
public function init() {
global $PAGE;
global $COURSE;
$this->title = get_config('gradelevel', 'blocktitle');
if(empty($this->title))
{
$this->title = get_string('title', 'block_gradelevel');
}
// include javascript and run badge renderer when page loading is complete
$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init');
// find or create the levelset for this course
$this->levelset = block_gradelevel_levelset::find_by_course($COURSE->id);
if(empty($this->levelset))
{
// create a new levelset with the name of this course and attach
$this->levelset = block_gradelevel_levelset::create_new($COURSE->shortname);
$this->levelset->attach_course($COURSE->id);
}
}
// The PHP tag and the curly bracket for the class definition
// will only be closed after there is another function added in the next section.
public function html_attributes() {
$attributes = parent::html_attributes(); // Get default values
$attributes['class'] .= ' block_'. $this->name(); // Append our class to class attribute
return $attributes;
}
public function get_content() {
global $USER;
if ($this->content !== null) {
return $this->content;
}
$this->content = new stdClass;
// below can be a single call to $this->levelset->get_user_leveldata() once the need for debugging (fixed point total) is gone
$pointstotal = $this->levelset->get_levelset_grade($USER->id);
$level_info = $this->levelset->calculate_level($pointstotal);
$this->content->text = $this->render_badge($level_info->badge_color,$level_info->progress,$this->levelset->getIcon(),$level_info->level);
if($level_info->levelup_total > 0)
{
$this->content->footer = "<div class='pointinfo'>".get_string('levelup_at','block_gradelevel')." <span class='currentpoints'>{$level_info->points_in_level}</span>/<span class='leveluppoints'>{$level_info->levelup_total}</span></div>";
}
else
{
$this->content->footer = "<div class='pointinfo complete'>".get_string('levelup_done','block_gradelevel')."</div>";
}
return $this->content;
}
public function hide_header() { return false; }
public function has_config() { return true; }
private function render_badge($basecolor,$progress,$image=null,$level="1"){
global $CFG;
if(strncmp($image,"data: ",6) != 0)
{
$image_url = $CFG->wwwroot.$image;
}
else
{
$image_url = $image;
}
$html = "<figure class='levelbadge' data-color='{$basecolor}' data-progress='{$progress}' data-width='150' data-height='150' data-level='{$level}'>";
if(!empty($image))
{
$html .= "<img style='display:none' src='{$image_url}' />";
}
$html .= "</figure>";
return $html;
}
}

439
classes/levelset.php Normal file
View File

@ -0,0 +1,439 @@
<?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",
25 => "#2ad4ff",
50 => "#cd7f32",
100 => "#a6a6a6",
200 => "#f6ae00",
);
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 sou
$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' => $levelset_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 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
}
/**
* 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;
}
}

26
db/access.php Normal file
View File

@ -0,0 +1,26 @@
<?php
$capabilities = array(
'block/gradelevel:myaddinstance' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array(
'user' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/my:manageblocks'
),
'block/gradelevel:addinstance' => array(
'riskbitmask' => RISK_SPAM | RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_BLOCK,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/site:manageblocks'
),
);

44
db/install.xml Normal file
View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="blocks/gradelevel/db" VERSION="20180917" COMMENT="XMLDB file for Moodle blocks/gradelevel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="block_gradelevel_levelset" COMMENT="Level set table">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="128" NOTNULL="false" SEQUENCE="false" COMMENT="Name of this level set"/>
<FIELD NAME="parent_id" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Optional parent for this level set (references another level set)"/>
<FIELD NAME="icon" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="parent_id_idx" TYPE="foreign" FIELDS="parent_id" REFTABLE="block_gradelevel_levelset" REFFIELDS="id" COMMENT="Parent index for nested levelsets"/>
</KEYS>
</TABLE>
<TABLE NAME="block_gradelevel_levels" COMMENT="Level references">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="levelset_id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Reference to specific levelset"/>
<FIELD NAME="points" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Number of points needed to reach this level"/>
<FIELD NAME="bagdecolor" TYPE="char" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Color of the level badge. Leave null to use default color (of the levelset 0 set)"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="levelset_id_idx" TYPE="foreign" FIELDS="levelset_id" REFTABLE="block_gradelevel_levelset" REFFIELDS="id" COMMENT="Index for levelset reference"/>
</KEYS>
</TABLE>
<TABLE NAME="block_gradelevel_course_link" COMMENT="Link courses to level sets">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="course_id" TYPE="int" LENGTH="20" NOTNULL="true" SEQUENCE="false" COMMENT="Id of the course to link"/>
<FIELD NAME="levelset_id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the levelset a course links to"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="course_id_idx" TYPE="foreign-unique" FIELDS="course_id" REFTABLE="mdl_course" REFFIELDS="id" COMMENT="Course Id index"/>
<KEY NAME="levelset_id_idx" TYPE="foreign" FIELDS="levelset_id" REFTABLE="block_gradelevel_levelset" REFFIELDS="id" COMMENT="Levelset id reference"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>

70
db/upgrade.php Normal file
View File

@ -0,0 +1,70 @@
<?php
function xmldb_block_gradelevel_upgrade($oldversion) {
global $DB;
$dbman = $DB->get_manager();
/// Add a new column newcol to the mdl_myqtype_options
if ($oldversion < 2018091600) {
// Define table block_gradelevel_levelset to be created.
$table = new xmldb_table('block_gradelevel_levelset');
// Adding fields to table block_gradelevel_levelset.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('name', XMLDB_TYPE_CHAR, '128', null, null, null, null);
$table->add_field('parent_id', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
$table->add_field('icon', XMLDB_TYPE_TEXT, null, null, null, null, null);
// Adding keys to table block_gradelevel_levelset.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('parent_id_idx', XMLDB_KEY_FOREIGN, array('parent_id'), 'block_gradelevel_levelset', array('id'));
// Conditionally launch create table for block_gradelevel_levelset.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table block_gradelevel_levels to be created.
$table = new xmldb_table('block_gradelevel_levels');
// Adding fields to table block_gradelevel_levels.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('levelset_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('points', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('badgecolor', XMLDB_TYPE_CHAR, '10', null, null, null, null);
// Adding keys to table block_gradelevel_levels.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('levelset_id_idx', XMLDB_KEY_FOREIGN, array('levelset_id'), 'block_gradelevel_levelset', array('id'));
// Conditionally launch create table for block_gradelevel_levels.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table block_gradelevel_course_link to be created.
$table = new xmldb_table('block_gradelevel_course_link');
// Adding fields to table block_gradelevel_course_link.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('course_id', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, null);
$table->add_field('levelset_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table block_gradelevel_course_link.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('course_id_idx', XMLDB_KEY_FOREIGN_UNIQUE, array('course_id'), 'mdl_course', array('id'));
$table->add_key('levelset_id_idx', XMLDB_KEY_FOREIGN, array('levelset_id'), 'block_gradelevel_levelset', array('id'));
// Conditionally launch create table for block_gradelevel_course_link.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Gradelevel savepoint reached.
upgrade_block_savepoint(true, 2018091600, 'gradelevel');
}
return true;
}

185
doc_USER_GRADE.txt Normal file
View File

@ -0,0 +1,185 @@
USER: stdClass Object
(
[id] => 2
[auth] => manual
[confirmed] => 1
[policyagreed] => 0
[deleted] => 0
[suspended] => 0
[mnethostid] => 1
[username] => admin
[idnumber] =>
[firstname] => Admin
[lastname] => User
[email] => pm@miqra.nl
[emailstop] => 0
[icq] =>
[skype] =>
[yahoo] =>
[aim] =>
[msn] =>
[phone1] =>
[phone2] =>
[institution] =>
[department] =>
[address] =>
[city] =>
[country] =>
[lang] => en
[calendartype] => gregorian
[theme] =>
[timezone] => 99
[firstaccess] => 1530717879
[lastaccess] => 1536959033
[lastlogin] => 1530732921
[currentlogin] => 1536953353
[lastip] => 2001:985:a081:1:5dfc:c799:21e1:4fdc
[secret] =>
[picture] => 0
[url] =>
[descriptionformat] => 1
[mailformat] => 1
[maildigest] => 0
[maildisplay] => 1
[autosubscribe] => 1
[trackforums] => 0
[timecreated] => 0
[timemodified] => 1530717931
[trustbitmask] => 0
[imagealt] =>
[lastnamephonetic] =>
[firstnamephonetic] =>
[middlename] =>
[alternatename] =>
[lastcourseaccess] => Array
(
[2] => 1530738819
)
[currentcourseaccess] => Array
(
[2] => 1536959033
)
[groupmember] => Array
(
)
[profile] => Array
(
)
[sesskey] => ejGT6VqIBv
[ajax_updatable_user_prefs] => Array
(
[drawer-open-nav] => alpha
[filepicker_recentrepository] => int
[filepicker_recentlicense] => safedir
[filepicker_recentviewmode] => int
[filemanager_recentviewmode] => int
)
[editing] => 1
[preference] => Array
(
[core_message_migrate_data] => 1
[auth_manual_passwordupdatetime] => 1530717931
[email_bounce_count] => 1
[email_send_count] => 1
[filepicker_recentrepository] => 4
[filepicker_recentlicense] => allrightsreserved
[login_failed_count_since_success] => 6
[ifirst] =>
[ilast] =>
[_lastloaded] => 1536959033
)
[enrol] => Array
(
[enrolled] => Array
(
[2] => 2147483647
)
[tempguest] => Array
(
)
)
[grade_last_report] => Array
(
[2] => grader
)
[gradeediting] => Array
(
[2] => 0
)
[access] => Array
(
[ra] => Array
(
[/1] => Array
(
[7] => 7
)
[/1/2] => Array
(
[8] => 8
)
[/1/40/23] => Array
(
[3] => 3
[5] => 5
)
)
[time] => 1536958657
[rsw] => Array
(
)
)
)
COURSE: stdClass Object
(
[id] => 2
[category] => 2
[sortorder] => 20001
[fullname] => PIC Microcontrollers programmeren
[shortname] => PIC-MCU3
[idnumber] =>
[summary] => Programmeren van PIC18 microcontrollers van microchip.
[summaryformat] => 1
[format] => topics
[showgrades] => 1
[newsitems] => 0
[startdate] => 1526594400
[enddate] => 0
[marker] => 0
[maxbytes] => 0
[legacyfiles] => 0
[showreports] => 0
[visible] => 1
[visibleold] => 1
[groupmode] => 0
[groupmodeforce] => 0
[defaultgroupingid] => 0
[lang] =>
[calendartype] =>
[theme] =>
[timecreated] => 1526555879
[timemodified] => 1536956992
[requested] => 0
[enablecompletion] => 1
[completionnotify] => 0
[cacherev] => 1536956992
)

110
edit_form.php Normal file
View File

@ -0,0 +1,110 @@
<?php
require_once($CFG->dirroot.'/blocks/gradelevel/lib.php');
class block_gradelevel_edit_form extends block_edit_form {
protected function specific_definition($mform) {
global $CFG;
// retrieve levelset
$levelset = $this->block->levelset;
// Section header title according to language file.
$mform->addElement('header', 'config_header', get_string('blocksettings', 'block'));
// A sample string variable with a default value.
$mform->addElement('text', 'levelset_name', get_string('levelset_name', 'block_gradelevel'));
$icon = $levelset->getIcon();
if(strncmp($icon,"data: ",6) != 0)
{
$icon = $CFG->wwwroot.$icon;
}
$icon_title = get_string('levelset_icon_cur', 'block_gradelevel');
$mform->addElement('html',"<div class='form-group row fitem'><div class='col-md-3'>{$icon_title}</div><div class='col-md-9'><figure class='levelset_icon'><img src='{$icon}' /></figure></div></div>");
// A sample string variable with a default value.
$mform->addElement('filepicker', 'levelset_icon', get_string('levelset_icon_new', 'block_gradelevel'), null,
array('maxbytes' => $maxbytes, 'accepted_types' => array('.png', '.svg')));
}
public function set_data($defaults)
{
// retrieve levelset
$levelset = $this->block->levelset;
$defaults->levelset_name = $levelset->getName();
$defaults->levelset_icon = $levelset->getIcon();
parent::set_data($defaults);
}
public function get_data()
{
$data = parent::get_data();
if(isset($data))
{
$this->save_data($data);
}
return $data;
}
protected function save_data($data)
{
// retrieve levelset
$levelset = $this->block->levelset;
$this->debug("data: ".print_r($data,true));
if(isset($data->levelset_name)){
$levelset->setName($data->levelset_name);
}
if(isset($data->levelset_icon))
{
$filename = $this->get_new_filename('levelset_icon');
$contents = $this->get_file_content('levelset_icon');
$imageData = base64_encode($contents);
$pi = pathinfo($filename);
if($pi['extension'] == 'svg')
{
$mime = "image/svg+xml";
}
else if($pi['extension'] == 'png')
{
$mime = "image/png";
}
else
{
$mime = "binary/octet-stream"; // no idea what it is
}
$dataSrc = 'data: '.$mime.';base64,'.$imageData;
$levelset->setIcon($dataSrc);
}
$this->debug("levelset: ".print_r($levelset,true));
$levelset->save_data();
}
private function debug($msg)
{
error_log($msg, 3, '/tmp/moodledevlog.log');
}
}

View File

@ -0,0 +1,18 @@
<?php
$string['pluginname'] = 'Grade Level';
$string['gradelevel'] = 'GradeLevel';
$string['title'] = 'Progress level';
$string['gradelevel:addinstance'] = 'Add a new Grade Level block';
$string['gradelevel:myaddinstance'] = 'Add a new Grade Level block to the My Moodle page';
$string['blockstring'] = 'Text in the block';
$string['levelup_at'] = 'Next level: ';
$string['levelup_done'] = 'Complete';
$string['levelset_name'] = "Skill name";
$string['levelset_icon_cur'] = "Current skill icon (for level badge)";
$string['levelset_icon_new'] = "New skill icon (for level badge)";
$string['headerconfig'] = "Grade Level settings";
$string['descconfig'] = "Configuration for Grade Level block";
$string['labeltitle'] = "Block title";
$string['desctitle'] = "The title of the gradelevel block";

4
lib.php Normal file
View File

@ -0,0 +1,4 @@
<?php
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/grade/querylib.php');

3
package-lock.json generated Normal file
View File

@ -0,0 +1,3 @@
{
"lockfileVersion": 1
}

BIN
pix/undefinedskill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

772
pix/undefinedskill.svg Normal file
View File

@ -0,0 +1,772 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="25mm"
height="25mm"
viewBox="0 0 25.000001 25"
version="1.1"
id="svg11763"
inkscape:version="0.92.1 r15371"
sodipodi:docname="base.svg">
<defs
id="defs11757">
<linearGradient
id="linearGradient26476"
inkscape:collect="always">
<stop
id="stop26472"
offset="0"
style="stop-color:#f0f0f0;stop-opacity:1" />
<stop
id="stop26474"
offset="1"
style="stop-color:#a6a6a6;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient7518"
inkscape:collect="always">
<stop
id="stop7514"
offset="0"
style="stop-color:#fbfbfb;stop-opacity:0.31764707" />
<stop
id="stop7516"
offset="1"
style="stop-color:#ffffff;stop-opacity:0.1255814" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12749"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12765"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12781"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12797"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12813"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12829"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12845"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12861"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12877"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12893"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8574"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker12913"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path12911"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker12917"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path12915"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient12919"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker12937"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path12935"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient26476"
id="radialGradient13236"
gradientUnits="userSpaceOnUse"
cx="50"
cy="92"
fx="50"
fy="92"
r="15.5" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7518"
id="linearGradient13238"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-35.116754,0.0704092)"
x1="80"
y1="98"
x2="87.273346"
y2="109.53979" />
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend-5"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8574-9"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker13272"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path13270"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker13276"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path13274"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker13280"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path13278"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker13284"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path13282"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="Arrow2Mend-2"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path8574-2"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)"
inkscape:connector-curvature="0" />
</marker>
<linearGradient
id="linearGradient3314">
<stop
offset="0"
style="stop-color:#dcdcdc;stop-opacity:1"
id="stop3316" />
<stop
offset="0.0729612"
style="stop-color:#e8e8e8;stop-opacity:1"
id="stop3322" />
<stop
offset="0.39346588"
style="stop-color:#e2e2e2;stop-opacity:1"
id="stop3324" />
<stop
offset="0.50066584"
style="stop-color:#bcbcbc;stop-opacity:1"
id="stop3326" />
<stop
offset="0.6425212"
style="stop-color:#dedede;stop-opacity:1"
id="stop3330" />
<stop
offset="0.94838297"
style="stop-color:#dddddd;stop-opacity:1"
id="stop3328" />
<stop
offset="1"
style="stop-color:#dcdcdc;stop-opacity:1"
id="stop3318" />
</linearGradient>
<linearGradient
id="linearGradient3298">
<stop
offset="0"
style="stop-color:#919191;stop-opacity:1"
id="stop3300" />
<stop
offset="0.30858582"
style="stop-color:#919191;stop-opacity:1"
id="stop3302" />
<stop
offset="0.51132685"
style="stop-color:#5c5c5c;stop-opacity:1"
id="stop3304" />
<stop
offset="0.69885707"
style="stop-color:#919191;stop-opacity:1"
id="stop3306" />
<stop
offset="1"
style="stop-color:#919191;stop-opacity:1"
id="stop3308" />
</linearGradient>
<linearGradient
id="linearGradient3270">
<stop
offset="0"
style="stop-color:#929295;stop-opacity:1"
id="stop3272" />
<stop
offset="1"
style="stop-color:#b9b9b9;stop-opacity:1"
id="stop3274" />
</linearGradient>
<linearGradient
id="linearGradient3246">
<stop
offset="0"
style="stop-color:#007d12;stop-opacity:1"
id="stop3248" />
<stop
offset="0.46926689"
style="stop-color:#007d12;stop-opacity:1"
id="stop3254" />
<stop
offset="0.51132685"
style="stop-color:#5e7c62;stop-opacity:1"
id="stop3256" />
<stop
offset="0.5669955"
style="stop-color:#007d12;stop-opacity:1"
id="stop3258" />
<stop
offset="1"
style="stop-color:#007d12;stop-opacity:1"
id="stop3250" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient3246"
id="linearGradient3252"
y2="299.70178"
x2="849.7998"
y1="463.29184"
x1="153.0388" />
<linearGradient
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient3270"
id="linearGradient3276"
y2="418.82431"
x2="241.64479"
y1="425.34058"
x1="171.59496" />
<linearGradient
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient3270"
id="linearGradient3286"
y2="294.47226"
x2="755.3006"
y1="269.49326"
x1="830.32367" />
<linearGradient
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient3298"
id="linearGradient3296"
y2="450.36261"
x2="896.48633"
y1="600.70026"
x1="197.66" />
<linearGradient
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient3314"
id="linearGradient3320"
y2="265.29617"
x2="780.50598"
y1="396.72012"
x1="233.33572" />
<filter
id="filter3587">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="9.2928517"
id="feGaussianBlur3589" />
</filter>
<filter
id="filter3631">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="14.191705"
id="feGaussianBlur3633" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="125.57529"
inkscape:cy="10.242677"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1672"
inkscape:window-y="-8"
inkscape:window-maximized="1"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid14028"
units="mm"
spacingx="1"
spacingy="1"
dotted="true"
empspacing="5" />
<inkscape:grid
type="xygrid"
id="grid14036" />
</sodipodi:namedview>
<metadata
id="metadata11760">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-96.700309,-96.926329)">
<g
inkscape:transform-center-y="2.0000018"
inkscape:transform-center-x="1.7354122"
id="use5329"
transform="matrix(204.05634,0,0,204.05634,-135784.74,75153.402)">
<rect
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect12450"
width="4"
height="11"
x="273.52917"
y="-149" />
<rect
y="-146"
x="277.52917"
height="5"
width="3"
id="rect12452"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 281,-145.00002 5,2e-5 v 0.47083 h 2 V -144 h 5 v 1 h -5 v 0.52917 h -2 V -142 l -5,-2e-5 z"
id="path12454"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccc" />
<g
id="g12460"
transform="translate(0.52916667,-2)">
<path
inkscape:connector-curvature="0"
id="path12456"
d="m 287.99999,-140.47083 v 2 H 290 Z"
style="fill:#ffffff;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="-138"
x="288"
height="7"
width="2"
id="rect12458"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
<g
inkscape:transform-center-y="2.0000018"
inkscape:transform-center-x="1.7354122"
id="use5329-5"
transform="matrix(204.05634,0,0,204.05634,-135784.74,75153.402)">
<rect
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect12508"
width="4"
height="11"
x="273.52917"
y="-149" />
<rect
y="-146"
x="277.52917"
height="5"
width="3"
id="rect12510"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 281,-145.00002 5,2e-5 v 0.47083 h 2 V -144 h 5 v 1 h -5 v 0.52917 h -2 V -142 l -5,-2e-5 z"
id="path12512"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccc" />
<g
id="g12518"
transform="translate(0.52916667,-2)">
<path
inkscape:connector-curvature="0"
id="path12514"
d="m 287.99999,-140.47083 v 2 H 290 Z"
style="fill:#ffffff;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="-138"
x="288"
height="7"
width="2"
id="rect12516"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
<g
id="use26239"
transform="translate(41252296,-26435496)">
<g
transform="matrix(157375.71,0,0,157375.71,-87067038,35086817)"
id="g13228">
<circle
style="opacity:1;fill:url(#radialGradient13236);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="circle13220"
cx="45"
cy="97"
r="15" />
<path
style="opacity:1;fill:url(#linearGradient13238);fill-opacity:1;stroke:none;stroke-width:0.97674406;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 58.593623,92.350616 c 2.385905,6.962784 -0.972423,14.538754 -7.252029,17.704604 -6.581851,3.31823 -14.445868,1.16906 -18.535439,-4.95045"
id="path13222"
inkscape:connector-curvature="0"
inkscape:transform-center-x="-1.0852379"
inkscape:transform-center-y="4.9804617"
sodipodi:nodetypes="csc" />
<circle
r="15"
cy="97"
cx="45"
id="circle13224"
style="opacity:1;fill:none;fill-opacity:1;stroke:#ebebeb;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
sodipodi:open="true"
d="m 44.897568,82.00035 a 15,15 0 0 1 14.369022,10.36668 15,15 0 0 1 -5.536793,16.83095 15,15 0 0 1 -17.71726,-0.18859"
sodipodi:end="2.2132531"
sodipodi:start="4.7055601"
sodipodi:ry="15"
sodipodi:rx="15"
sodipodi:cy="97"
sodipodi:cx="45"
sodipodi:type="arc"
style="opacity:1;fill:none;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path13226" />
</g>
<g
id="g13232">
<g
transform="matrix(3.7795276,0,0,3.7795276,-2219.735,1371.8767)"
id="use13230"
inkscape:transform-center-x="0.99999954"
inkscape:transform-center-y="0.60193146">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.26458332"
x="240"
y="-105"
id="text13242"><tspan
sodipodi:role="line"
id="tspan13240"
x="240"
y="-105"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.28888893px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans';fill:#ffffff;stroke:#ffffff;stroke-width:0.26458332">M</tspan></text>
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path13244"
d="m 258,-101 -3,4 h 6 z"
style="fill:#ffffff;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<circle
r="2"
cy="-99"
cx="241"
id="circle13246"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path13248"
d="m 237,-101.47083 h 24"
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path13250"
d="m 249,-100 v 7"
style="fill:none;stroke:#f9f9f9;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" />
<text
id="text13256"
y="-91.737373"
x="250.36359"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
xml:space="preserve"><tspan
style="font-size:3.52777767px;fill:#ffffff;stroke-width:0.26458332"
y="-91.737373"
x="250.36359"
id="tspan13254"
sodipodi:role="line">F<tspan
id="tspan13252"
style="font-size:1.41111112px;fill:#ffffff">Z</tspan></tspan></text>
</g>
</g>
</g>
<g
transform="matrix(204.05634,0,0,204.05634,-135917.88,75008.797)"
id="use5478"
inkscape:transform-center-x="0.99999954"
inkscape:transform-center-y="0.60193146">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.26458332"
x="240"
y="-105"
id="text13402"><tspan
sodipodi:role="line"
id="tspan13400"
x="240"
y="-105"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.28888893px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans';fill:#ffffff;stroke:#ffffff;stroke-width:0.26458332">M</tspan></text>
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path13404"
d="m 258,-101 -3,4 h 6 z"
style="fill:#ffffff;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<circle
r="2"
cy="-99"
cx="241"
id="circle13406"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path13408"
d="m 237,-101.47083 h 24"
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path13410"
d="m 249,-100 v 7"
style="fill:none;stroke:#f9f9f9;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" />
<text
id="text13416"
y="-91.737373"
x="250.36359"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
xml:space="preserve"><tspan
style="font-size:3.52777767px;fill:#ffffff;stroke-width:0.26458332"
y="-91.737373"
x="250.36359"
id="tspan13414"
sodipodi:role="line">F<tspan
id="tspan13412"
style="font-size:1.41111112px;fill:#ffffff">Z</tspan></tspan></text>
</g>
<text
id="text26490"
y="108.77583"
x="98.945969"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.26458332"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.69999981px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans';fill:#ffffff;stroke:#ffffff;stroke-width:0.26458332"
y="108.77583"
x="98.945969"
id="tspan26488"
sodipodi:role="line">S</tspan></text>
<path
id="path4840"
d="m 116.70664,100.3512 c -0.14496,-0.002 -0.29126,0.006 -0.43659,0.01 -1.17718,0.0362 -2.33828,0.9636 -2.42672,2.18313 -0.29727,1.25439 -0.50828,2.57197 -1.15103,3.70509 -2.1609,2.16336 -4.48161,4.16303 -6.70887,6.25817 -0.90253,0.86304 -1.86084,1.67552 -2.6889,2.61168 -0.6963,1.00578 0.0217,2.73204 1.30443,2.72455 1.01064,-0.0331 1.59865,-1.0163 2.25453,-1.64313 2.44276,-2.6581 4.68431,-5.49976 7.19592,-8.09519 0.66341,-0.54689 1.58373,-0.59881 2.3884,-0.81925 0.54078,-0.13455 1.11301,-0.17869 1.63466,-0.36816 0.9358,-0.59434 1.60138,-1.6614 1.70207,-2.76612 0.0333,-0.26301 -0.26095,-0.18064 -0.26095,-0.18064 l -1.62107,0.74703 -1.23842,-1.02247 -0.20786,-1.15326 c 0,0 0.37664,-0.37309 0.54518,-0.40539 l 1.59106,-0.59363 c -0.0633,-0.65793 -0.8946,-0.98875 -1.44713,-1.1386 -0.14025,-0.0381 -0.28388,-0.0511 -0.42884,-0.0536 z m -1.20876,4.4606 0.72444,1.71223 -0.65567,0.26423 -0.0142,-0.0329 0.17926,-0.0746 -0.0342,-0.082 0.0375,-0.009 0.0862,-0.0193 -0.0386,-0.17219 -0.086,0.0191 -0.068,0.0152 -0.0859,-0.20546 0.0367,-0.009 0.0862,-0.0193 -0.0384,-0.1722 -0.0862,0.0192 -0.0674,0.015 -0.0859,-0.20563 0.0362,-0.008 0.0862,-0.0192 -0.0386,-0.17236 -0.086,0.0193 -0.0667,0.015 -0.0859,-0.20581 0.0353,-0.008 0.0862,-0.0191 -0.0384,-0.17236 -0.0862,0.0193 -0.0661,0.0149 -0.11963,-0.28682 c -0.12696,0.0512 -0.13397,0.0554 -0.19581,0.081 l -0.0148,-0.0347 c 0.22143,-0.0893 0.44287,-0.17845 0.66429,-0.26768 z m -1.15554,0.46556 0.0168,0.0393 c -0.0598,0.0247 -0.0613,0.0265 -0.17426,0.072 l 0.0576,0.13702 -0.0298,0.007 -0.0862,0.0193 0.0386,0.17218 0.086,-0.0193 0.0609,-0.0136 0.0865,0.20563 -0.0302,0.007 -0.0862,0.0193 0.0384,0.1722 0.0862,-0.0193 0.0612,-0.0136 0.0863,0.20546 -0.0305,0.007 -0.0862,0.0193 0.0386,0.17219 0.086,-0.0191 0.0617,-0.0138 0.0863,0.20545 -0.0308,0.007 -0.0862,0.0193 0.0384,0.1722 0.0862,-0.0192 0.0621,-0.0138 0.0978,0.23252 0.18598,-0.0774 0.0117,0.0273 -0.61448,0.24751 -0.7241,-1.71222 c 0.20185,-0.0814 0.40366,-0.16274 0.60552,-0.24406 z m -9.51337,9.71898 a 1.0178768,1.0178768 0 0 1 1.01779,1.01797 1.0178768,1.0178768 0 0 1 -1.01779,1.01779 1.0178768,1.0178768 0 0 1 -1.01779,-1.01779 1.0178768,1.0178768 0 0 1 1.01779,-1.01797 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;stroke-width:0.08825012" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

14
settings.php Normal file
View File

@ -0,0 +1,14 @@
<?php
$settings->add(new admin_setting_heading(
'gradelevel/headerconfig',
get_string('headerconfig', 'block_gradelevel'),
get_string('descconfig', 'block_gradelevel')
));
$settings->add(new admin_setting_configtext(
'gradelevel/blocktitle',
get_string('labeltitle', 'block_gradelevel'),
get_string('desctitle', 'block_gradelevel'),
get_string('title', 'block_gradelevel')
));

31
styles.css Normal file
View File

@ -0,0 +1,31 @@
figure.levelbadge {
text-align: center;
}
div.pointinfo {
text-align: center;
font-family: "Open Sans", Arial, helvetica, sans-serif;
}
div.pointinfo.complete {
font-weight: bold;
}
div.pointinfo span.currentpoints {
font-weight: bold;
}
div.pointinfo span.leveluppoints {
font-weight: bold;
}
figure.levelset_icon {
background-color: #ccc;
background-image: -moz-linear-gradient(45deg, #aaa 25%, transparent 25%), -moz-linear-gradient(-45deg, #aaa 25%, transparent 25%), -moz-linear-gradient(45deg, transparent 75%, #aaa 75%), -moz-linear-gradient(-45deg, transparent 75%, #aaa 75%);
background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, #aaa), color-stop(.25, transparent)), -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, #aaa), color-stop(.25, transparent)), -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.75, transparent), color-stop(.75, #aaa)), -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.75, transparent), color-stop(.75, #aaa));
-moz-background-size: 32px 32px;
background-size: 32px 33px;
-webkit-background-size: 32px 33px; /* override value for shitty webkit */
background-position: 0 0, 16px 0, 16px -16px, 0px 16px;
display: inline-block;
}

4
version.php Normal file
View File

@ -0,0 +1,4 @@
<?php
$plugin->component = 'block_gradelevel'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494)
$plugin->version = 2018091600; // YYYYMMDDHH (year, month, day, 24-hr time)
$plugin->requires = 2018050800; // YYYYMMDDHH (This is the release version for Moodle 3.5)