From d70463cfba7d09f7a4962d0a382e9a0936c36448 Mon Sep 17 00:00:00 2001 From: pmk Date: Mon, 17 Sep 2018 13:12:56 +0200 Subject: [PATCH] Commit of working grade level plugin Todo for first usable version: Add level customization per levelset (course) --- amd/src/renderbadge.js | 212 ++++++++++ block_gradelevel.php | 103 +++++ classes/levelset.php | 439 ++++++++++++++++++++ db/access.php | 26 ++ db/install.xml | 44 ++ db/upgrade.php | 70 ++++ doc_USER_GRADE.txt | 185 +++++++++ edit_form.php | 110 +++++ lang/en/block_gradelevel.php | 18 + lib.php | 4 + package-lock.json | 3 + pix/undefinedskill.png | Bin 0 -> 1969 bytes pix/undefinedskill.svg | 772 +++++++++++++++++++++++++++++++++++ settings.php | 14 + styles.css | 31 ++ version.php | 4 + 16 files changed, 2035 insertions(+) create mode 100644 amd/src/renderbadge.js create mode 100644 block_gradelevel.php create mode 100644 classes/levelset.php create mode 100644 db/access.php create mode 100644 db/install.xml create mode 100644 db/upgrade.php create mode 100644 doc_USER_GRADE.txt create mode 100644 edit_form.php create mode 100644 lang/en/block_gradelevel.php create mode 100644 lib.php create mode 100644 package-lock.json create mode 100644 pix/undefinedskill.png create mode 100644 pix/undefinedskill.svg create mode 100644 settings.php create mode 100644 styles.css create mode 100644 version.php diff --git a/amd/src/renderbadge.js b/amd/src/renderbadge.js new file mode 100644 index 0000000..856addf --- /dev/null +++ b/amd/src/renderbadge.js @@ -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 = $(""); + 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; +}); \ No newline at end of file diff --git a/block_gradelevel.php b/block_gradelevel.php new file mode 100644 index 0000000..5a989a4 --- /dev/null +++ b/block_gradelevel.php @@ -0,0 +1,103 @@ +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 = "
".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
"; + } + else + { + $this->content->footer = "
".get_string('levelup_done','block_gradelevel')."
"; + } + + + + 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 = "
"; + if(!empty($image)) + { + $html .= ""; + } + $html .= "
"; + return $html; + } + + + +} \ No newline at end of file diff --git a/classes/levelset.php b/classes/levelset.php new file mode 100644 index 0000000..e782aa6 --- /dev/null +++ b/classes/levelset.php @@ -0,0 +1,439 @@ +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; + + } + +} \ No newline at end of file diff --git a/db/access.php b/db/access.php new file mode 100644 index 0000000..2c212ad --- /dev/null +++ b/db/access.php @@ -0,0 +1,26 @@ + 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' + ), +); \ No newline at end of file diff --git a/db/install.xml b/db/install.xml new file mode 100644 index 0000000..609c00f --- /dev/null +++ b/db/install.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/db/upgrade.php b/db/upgrade.php new file mode 100644 index 0000000..e043fec --- /dev/null +++ b/db/upgrade.php @@ -0,0 +1,70 @@ +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; +} \ No newline at end of file diff --git a/doc_USER_GRADE.txt b/doc_USER_GRADE.txt new file mode 100644 index 0000000..e403f87 --- /dev/null +++ b/doc_USER_GRADE.txt @@ -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 +) diff --git a/edit_form.php b/edit_form.php new file mode 100644 index 0000000..a6f3723 --- /dev/null +++ b/edit_form.php @@ -0,0 +1,110 @@ +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',"
{$icon_title}
"); + + // 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'); + } +} \ No newline at end of file diff --git a/lang/en/block_gradelevel.php b/lang/en/block_gradelevel.php new file mode 100644 index 0000000..e68dc6d --- /dev/null +++ b/lang/en/block_gradelevel.php @@ -0,0 +1,18 @@ +libdir.'/gradelib.php'); +require_once($CFG->dirroot.'/grade/querylib.php'); + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..48e341a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} diff --git a/pix/undefinedskill.png b/pix/undefinedskill.png new file mode 100644 index 0000000000000000000000000000000000000000..b23c864d253a7e04f73caf75075c3bcaa99e3d62 GIT binary patch literal 1969 zcmV;i2Tu5jP)2oHo19!!#CykB)5w_piAfQK=PeK?45k)s$( zuOCSN|1-P`i>uZ@bRfZ6jHHgv%V_!#?!6b(I-14LB9=!B5-R+Q9emTAWyxpr@q{%hJGRT#a*Z z3Kn81F2d_@D|V%2kBD8=vQmWS0B^zW>0i|U^Jl!W3{kh@kv2|Wnd3Juz^C!8BDLlP z^YE1}9p1_W-dwb}J()^Ju(r@jO9Ef$!RbvJcuY~kP8Pd}R*qm{Ar+Pgu9}6@$M7{g zwkUDSO`XZ?DWtNJ!Tmir{I^(#Gm92?X8LqnIb3L^C4lGWaP}U21Wzhc;5>1P*h&LW zElorL;N4w1eJhq%C30&<%n@v?L`WSe%54;c_=Jv;GEoL;HWE=Xg8^pK2l?`PY^C&S+{TtK21r7Wdd8f1e)Oy=A zP94YZyKw$rr?K(nRT;JdpBMYZLmixdLq*z7)H&w$A7A9n>vE)+Bkm{@2Eh_?JXKs;R?qtP42{E zJVE{exM9#%*2I22yY{-dWDVz=Oh18}#IEO?8C@Sv%ik^^*OqBdSKvx<@w=5$GY!NH ziB|TCvy*Ew59CkjLt3eOexSk$Lr3t8Hjdv_V*RfXldQ6=6&La!YPoh;?1qx`AYDAC0+Uz8*hw!ufD2?41(VPwJ z7r#XB#`WS%G$E;{npYKIyk-;8n{;hvj8y@;!8R8{K6n7Wr+h% zLvJx`;v3Mw`^|Eo?x1fzzMWIkHbtf0^%Fcx9B`W9(0%E@YzX*}*st$RukR3JHH8eM z;+N--ayos73S%lguZ3b!2;saC!txNpvJm$@%?lyS4u5&;jn{q#qez&M)88GwGS=gE|dS{ zboybuv})}`3tnR81@$cB`kecCT!B~VZF}dss_ZMHuT_dVi0d|Lji+|7JOy+h}p;Y_2~3*TvfFVLkFHHe+ZzJfjp36pb>Lg{xJ)u-%_;=LkBJv2gF{v(}3+X z8F=tw`Qt9FY%!?WTZ4+;<1S5hscNV7u!1+}eeIT*V)FE=?HDTXHoe7NnjBTf)$@UC zXX*3_ys>IKh6=oW77lN6ebsgh72;uV*=*fx9_38jT(uoT1zzab1&rHpc6x6yK4Ip8 zwvMak1$VVLu9F?`Cy2+cWZ7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + M + + + + + FZ + + + + + M + + + + + FZ + + S + + + diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..6f8d358 --- /dev/null +++ b/settings.php @@ -0,0 +1,14 @@ +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') + )); \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..c32089a --- /dev/null +++ b/styles.css @@ -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; +} \ No newline at end of file diff --git a/version.php b/version.php new file mode 100644 index 0000000..0133c93 --- /dev/null +++ b/version.php @@ -0,0 +1,4 @@ +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) \ No newline at end of file