Skip to content

Commit

Permalink
MDL-63876 badges: Add competency criteria
Browse files Browse the repository at this point in the history
Properly integration badges with competencies provided in Moodle.

Automatically grant the badge when the defined competencies are marked as proficient.
  • Loading branch information
Damyon Wiese committed Mar 29, 2019
1 parent e8bfd9b commit f94786e
Show file tree
Hide file tree
Showing 15 changed files with 674 additions and 37 deletions.
2 changes: 1 addition & 1 deletion admin/tool/lp/classes/external.php
Expand Up @@ -205,7 +205,7 @@ public static function data_for_competencies_manage_page($competencyframeworkid,
self::validate_context($framework->get_context());
$output = $PAGE->get_renderer('tool_lp');

$renderable = new output\manage_competencies_page($framework, $params['search'], $framework->get_context());
$renderable = new output\manage_competencies_page($framework, $params['search'], $framework->get_context(), null);

$data = $renderable->export_for_template($output);

Expand Down
2 changes: 1 addition & 1 deletion admin/tool/lp/competencies.php
Expand Up @@ -25,9 +25,9 @@
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir.'/adminlib.php');

$id = required_param('competencyframeworkid', PARAM_INT);
$pagecontextid = required_param('pagecontextid', PARAM_INT); // Reference to the context we came from.
$search = optional_param('search', '', PARAM_RAW);
$id = required_param('competencyframeworkid', PARAM_INT);

require_login();
\core_competency\api::require_enabled();
Expand Down
14 changes: 11 additions & 3 deletions admin/tool/lp/styles.css
@@ -1,7 +1,8 @@
.path-admin-tool-lp [data-region="managecompetencies"] ul li,
.path-admin-tool-lp [data-region="plans"] ul li,
.path-admin-tool-lp [data-region="competencymovetree"] ul li,
.path-admin-tool-lp [data-region="competencylinktree"] ul li {
.path-admin-tool-lp [data-region="competencylinktree"] ul li,
.path-badges [data-region="competencylinktree"] ul li {
list-style-type: none;
}

Expand All @@ -22,6 +23,7 @@
.path-admin-tool-lp [data-region="managecompetencies"] ul[data-enhance="tree"],
.path-admin-tool-lp [data-region="plans"] ul[data-enhance="tree"],
.path-admin-tool-lp [data-region="competencylinktree"] ul[data-enhance="linktree"],
.path-badges [data-region="competencylinktree"] ul[data-enhance="linktree"],
.path-admin-tool-lp [data-region="competencymovetree"] ul[data-enhance="movetree"] {
border: 1px solid #ccc;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
Expand All @@ -36,11 +38,13 @@
.path-admin-tool-lp [data-region="managecompetencies"] ul,
.path-admin-tool-lp [data-region="plans"] ul,
.path-admin-tool-lp [data-region="competencylinktree"] ul,
.path-badges [data-region="competencylinktree"] ul,
.path-admin-tool-lp [data-region="competencymovetree"] ul {
cursor: pointer;
}

.path-admin-tool-lp [data-region="competencylinktree"] ul li > span,
.path-badges [data-region="competencylinktree"] ul li > span,
.path-admin-tool-lp [data-region="competencymovetree"] ul li > span,
.path-admin-tool-lp [data-region="plans"] ul li > span,
.path-admin-tool-lp [data-region="managecompetencies"] ul li > span {
Expand All @@ -52,13 +56,15 @@
}

.path-admin-tool-lp [data-region="competencylinktree"] ul [aria-selected="true"] > span,
.path-badges [data-region="competencylinktree"] ul [aria-selected="true"] > span,
.path-admin-tool-lp [data-region="competencymovetree"] ul [aria-selected="true"] > span,
.path-admin-tool-lp [data-region="plans"] ul [aria-selected="true"] > span,
.path-admin-tool-lp [data-region="managecompetencies"] ul [aria-selected="true"] > span {
background-color: #dfdfdf;
}

.path-admin-tool-lp [data-region="competencylinktree"] ul [tabindex="0"] > span,
.path-badges [data-region="competencylinktree"] ul [tabindex="0"] > span,
.path-admin-tool-lp [data-region="competencymovetree"] ul [tabindex="0"] > span,
.path-admin-tool-lp [data-region="plans"] ul [tabindex="0"] > span,
.path-admin-tool-lp [data-region="managecompetencies"] ul [tabindex="0"] > span {
Expand All @@ -74,7 +80,8 @@
text-align: center;
}

.path-admin-tool-lp [data-region="competencylinktree"] > ul {
.path-admin-tool-lp [data-region="competencylinktree"] > ul,
.path-badges [data-region="competencylinktree"] > ul {
overflow-y: auto;
height: 400px;
}
Expand Down Expand Up @@ -112,7 +119,8 @@

.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"] select,
.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select,
.path-admin-tool-lp [data-region="competencylinktree"] select {
.path-admin-tool-lp [data-region="competencylinktree"] select,
.path-badges [data-region="competencylinktree"] select {
width: 100%;
}

Expand Down
1 change: 1 addition & 0 deletions badges/amd/build/competency.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 136 additions & 0 deletions badges/amd/src/competency.js
@@ -0,0 +1,136 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Badge select competency actions
*
* @module core_badges/competency
* @package core
* @class competency
* @copyright 2019 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'tool_lp/competencypicker', 'core/ajax', 'core/notification', 'core/templates'],
function($, Picker, Ajax, Notification, Templates) {

var pickerInstance = null;

var pageContextId = 1;

/**
* Re-render the list of selected competencies.
*
* @method renderCompetencies
*/
var renderCompetencies = function() {
var currentCompetencies = $('[data-action="competencies"]').val();
var requests = [];
var i = 0;

if (currentCompetencies != '') {
currentCompetencies = currentCompetencies.split(',');
for (i = 0; i < currentCompetencies.length; i++) {
requests[requests.length] = {
methodname: 'core_competency_read_competency',
args: {id: currentCompetencies[i]}
};
}
}

$.when.apply($, Ajax.call(requests, false)).then(function() {
var i = 0,
competencies = [];

for (i = 0; i < arguments.length; i++) {
competencies[i] = arguments[i];
}
var context = {
competencies: competencies
};

return Templates.render('core_badges/award_criteria_competencies', context);
}).then(function(html, js) {
Templates.replaceNode($('[data-region="competencies"]'), html, js);
return true;
}).fail(Notification.exception);

return true;
};

/**
* Deselect a competency
*
* @method unpickCompetenciesHandler
*/
var unpickCompetenciesHandler = function(e) {
var currentCompetencies = $('[data-action="competencies"]').val().split(','),
newCompetencies = [],
i,
toRemove = $(e.currentTarget).data('id');

for (i = 0; i < currentCompetencies.length; i++) {
if (currentCompetencies[i] != toRemove) {
newCompetencies[newCompetencies.length] = currentCompetencies[i];
}
}

$('[data-action="competencies"]').val(newCompetencies.join(','));

return renderCompetencies();
};

/**
* Open a competencies popup to relate competencies.
*
* @method pickCompetenciesHandler
*/
var pickCompetenciesHandler = function() {
var currentCompetencies = $('[data-action="competencies"]').val().split(',');

if (!pickerInstance) {
pickerInstance = new Picker(pageContextId, false, 'parents', true);
pickerInstance.on('save', function(e, data) {
var before = $('[data-action="competencies"]').val();
var compIds = data.competencyIds;
if (before != '') {
compIds = compIds.concat(before.split(','));
}
var value = compIds.join(',');

$('[data-action="competencies"]').val(value);

return renderCompetencies();
});
}

pickerInstance.setDisallowedCompetencyIDs(currentCompetencies);
pickerInstance.display();
};

return /** @alias module:core_badges/competency */ {
/**
* Listen for clicks on the competency picker and push the changes to the form element.
*
* @method init
* @param {Integer} contextId
*/
init: function(contextId) {
pageContextId = contextId;
renderCompetencies();
$('[data-action="select-competencies"]').on('click', pickCompetenciesHandler);
$('body').on('click', '[data-action="deselect-competency"]', unpickCompetenciesHandler);
}
};
});
42 changes: 42 additions & 0 deletions badges/classes/observer.php
Expand Up @@ -69,6 +69,48 @@ public static function course_module_criteria_review(\core\event\course_module_c
}
}

/**
* Triggered when '\core\event\competency_evidence_created' event is triggered.
*
* @param \core\event\competency_evidence_created $event
*/
public static function competency_criteria_review(\core\event\competency_evidence_created $event) {
global $DB, $CFG;

if (!empty($CFG->enablebadges)) {
require_once($CFG->dirroot.'/lib/badgeslib.php');

if (!get_config('core_competency', 'enabled')) {
return;
}

$ucid = $event->other['usercompetencyid'];
$cid = $event->other['competencyid'];
$eventdata = $event->get_record_snapshot('competency_usercomp', $ucid);
$userid = $event->relateduserid;

if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'competency_' . $cid, 'value' => $cid))) {
foreach ($rs as $r) {
$crit = $DB->get_record('badge_criteria', array('id' => $r->critid), 'badgeid, criteriatype', MUST_EXIST);
$badge = new badge($crit->badgeid);
// Only site badges are updated from site competencies.
if (!$badge->is_active() || $badge->is_issued($userid)) {
continue;
}

if ($badge->criteria[$crit->criteriatype]->review($userid)) {
$badge->criteria[$crit->criteriatype]->mark_complete($userid);

if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
$badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
$badge->issue($userid);
}
}
}
}
}
}

/**
* Triggered when 'course_completed' event is triggered.
*
Expand Down
25 changes: 16 additions & 9 deletions badges/criteria/award_criteria.php
Expand Up @@ -80,20 +80,27 @@
*/
define('BADGE_CRITERIA_TYPE_COHORT', 8);

/*
* Competency criteria type
* Criteria type constant, primarily for storing criteria type in the database.
*/
define('BADGE_CRITERIA_TYPE_COMPETENCY', 9);

/*
* Criteria type constant to class name mapping
*/
global $BADGE_CRITERIA_TYPES;
$BADGE_CRITERIA_TYPES = array(
BADGE_CRITERIA_TYPE_OVERALL => 'overall',
BADGE_CRITERIA_TYPE_ACTIVITY => 'activity',
BADGE_CRITERIA_TYPE_MANUAL => 'manual',
BADGE_CRITERIA_TYPE_SOCIAL => 'social',
BADGE_CRITERIA_TYPE_COURSE => 'course',
BADGE_CRITERIA_TYPE_COURSESET => 'courseset',
BADGE_CRITERIA_TYPE_PROFILE => 'profile',
BADGE_CRITERIA_TYPE_BADGE => 'badge',
BADGE_CRITERIA_TYPE_COHORT => 'cohort',
BADGE_CRITERIA_TYPE_OVERALL => 'overall',
BADGE_CRITERIA_TYPE_ACTIVITY => 'activity',
BADGE_CRITERIA_TYPE_MANUAL => 'manual',
BADGE_CRITERIA_TYPE_SOCIAL => 'social',
BADGE_CRITERIA_TYPE_COURSE => 'course',
BADGE_CRITERIA_TYPE_COURSESET => 'courseset',
BADGE_CRITERIA_TYPE_PROFILE => 'profile',
BADGE_CRITERIA_TYPE_BADGE => 'badge',
BADGE_CRITERIA_TYPE_COHORT => 'cohort',
BADGE_CRITERIA_TYPE_COMPETENCY => 'competency',
);

/**
Expand Down

0 comments on commit f94786e

Please sign in to comment.