MDL-63876 badges: Add competency criteria

Properly integration badges with competencies provided in Moodle.

Automatically grant the badge when the defined competencies are marked as proficient.
This commit is contained in:
Damyon Wiese 2019-01-17 11:35:57 +08:00
parent e8bfd9b4a9
commit f94786e0e1
15 changed files with 674 additions and 37 deletions

View File

@ -205,7 +205,7 @@ class external extends external_api {
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);

View File

@ -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();

View File

@ -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;
}
@ -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);
@ -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 {
@ -52,6 +56,7 @@
}
.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 {
@ -59,6 +64,7 @@
}
.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 {
@ -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;
}
@ -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%;
}

1
badges/amd/build/competency.min.js vendored Normal file
View File

@ -0,0 +1 @@
define(["jquery","tool_lp/competencypicker","core/ajax","core/notification","core/templates"],function(a,b,c,d,e){var f=null,g=1,h=function(){var b=a('[data-action="competencies"]').val(),f=[],g=0;if(""!=b)for(b=b.split(","),g=0;g<b.length;g++)f[f.length]={methodname:"core_competency_read_competency",args:{id:b[g]}};return a.when.apply(a,c.call(f,!1)).then(function(){var a=0,b=[];for(a=0;a<arguments.length;a++)b[a]=arguments[a];var c={competencies:b};return e.render("core_badges/award_criteria_competencies",c)}).then(function(b,c){return e.replaceNode(a('[data-region="competencies"]'),b,c),!0}).fail(d.exception),!0},i=function(b){var c,d=a('[data-action="competencies"]').val().split(","),e=[],f=a(b.currentTarget).data("id");for(c=0;c<d.length;c++)d[c]!=f&&(e[e.length]=d[c]);return a('[data-action="competencies"]').val(e.join(",")),h()},j=function(){var c=a('[data-action="competencies"]').val().split(",");f||(f=new b(g,(!1),"parents",(!0)),f.on("save",function(b,c){var d=a('[data-action="competencies"]').val(),e=c.competencyIds;""!=d&&(e=e.concat(d.split(",")));var f=e.join(",");return a('[data-action="competencies"]').val(f),h()})),f.setDisallowedCompetencyIDs(c),f.display()};return{init:function(b){g=b,h(),a('[data-action="select-competencies"]').on("click",j),a("body").on("click",'[data-action="deselect-competency"]',i)}}});

View File

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

View File

@ -69,6 +69,48 @@ class core_badges_observer {
}
}
/**
* 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.
*

View File

@ -80,20 +80,27 @@ define('BADGE_CRITERIA_TYPE_BADGE', 7);
*/
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',
);
/**

View File

@ -0,0 +1,237 @@
<?php
// 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/>.
/**
* This file contains the badge earned badge award criteria type class
*
* @package core
* @subpackage badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Badge award criteria -- award on competency completion
*
* @package core
* @subpackage badges
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class award_criteria_competency extends award_criteria {
/* @var int Criteria [BADGE_CRITERIA_TYPE_COMPETENCY] */
public $criteriatype = BADGE_CRITERIA_TYPE_COMPETENCY;
public $required_param = 'competency';
public $self_validation = true;
public $optional_params = array('');
/**
* Get criteria details for displaying to users
* @param string $short Print short version of criteria
* @return string
*/
public function get_details($short = '') {
global $DB, $OUTPUT;
$output = array();
foreach ($this->params as $p) {
$competency = new \core_competency\competency($p['competency']);
if ($short) {
$competency->set('description', '');
}
$summary = new \tool_lp\output\competency_summary($competency, $competency->get_framework(), !$short, !$short);
$str = $OUTPUT->render($summary);
$output[] = $str;
}
return '<dl><dd class="p-3 mb-2 bg-light text-dark border">' .
implode('</dd><dd class="p-3 mb-2 bg-light text-dark border">', $output) .
'</dd></dl>';
}
/**
* Add appropriate new criteria options to the form
* @param object $mform moodle form
*/
public function get_options(&$mform) {
global $DB, $PAGE;
$none = false;
$availablebadges = null;
$mform->addElement('header', 'first_header', $this->get_title());
$mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges');
// Determine if this badge is a course badge or a site badge.
$competencies = '';
if (count($this->params)) {
$competencies = implode(',', array_keys($this->params));
}
$mform->addElement('static', 'competenciesdescription', '', '<div data-region="competencies"></div>');
$mform->addElement('hidden', 'competency', $competencies, ['data-action' => 'competencies']);
$mform->setType('competency', PARAM_RAW);
$badge = $DB->get_record('badge', array('id' => $this->badgeid));
if ($badge->type == BADGE_TYPE_SITE) {
$context = context_system::instance();
} else if ($badge->type == BADGE_TYPE_COURSE) {
$context = context_course::instance($badge->courseid);
}
$params = [$context->id];
// Require some JS to select the competencies.
$PAGE->requires->js_call_amd('core_badges/competency', 'init', $params);
$mform->addElement('button', 'select_competencies', get_string('addcompetency', 'badges'), ['data-action' => 'select-competencies']);
// Add aggregation.
if (!$none) {
$mform->addElement('header', 'aggregation', get_string('method', 'badges'));
$agg = array();
$agg[] =& $mform->createElement('radio', 'agg', '', get_string('allmethodcompetencies', 'badges'), 1);
$agg[] =& $mform->createElement('radio', 'agg', '', get_string('anymethodcompetencies', 'badges'), 2);
$mform->addGroup($agg, 'methodgr', '', array('<br/>'), false);
if ($this->id !== 0) {
$mform->setDefault('agg', $this->method);
} else {
$mform->setDefault('agg', BADGE_CRITERIA_AGGREGATION_ANY);
}
}
return array($none, get_string('noparamstoadd', 'badges'));
}
/**
* Save criteria records
*
* @param array $params Values from the form or any other array.
*/
public function save($params = array()) {
$competencies = explode(',', $params['competency']);
unset($params['competency']);
foreach ($competencies as $competencyid) {
$params["competency_{$competencyid}"] = $competencyid;
}
parent::save($params);
}
/**
* Review this criteria and decide if it has been completed
*
* @param int $userid User whose criteria completion needs to be reviewed.
* @param bool $filtered An additional parameter indicating that user list
* has been reduced and some expensive checks can be skipped.
*
* @return bool Whether criteria is complete.
*/
public function review($userid, $filtered = false) {
global $DB;
$overall = false;
$competencyids = [];
foreach ($this->params as $param) {
$competencyids[] = $param['competency'];
}
$existing = [];
$badge = $DB->get_record('badge', array('id' => $this->badgeid));
if ($badge->type == BADGE_TYPE_SITE) {
$existing = \core_competency\user_competency::get_multiple($userid, $competencyids);
} else if ($badge->type == BADGE_TYPE_COURSE) {
$existing = \core_competency\user_competency_course::get_multiple($userid, $badge->courseid, $competencyids);
}
foreach ($this->params as $param) {
$found = false;
$proficiency = false;
foreach ($existing as $usercompetency) {
if ($usercompetency->get('competencyid') == $param['competency']) {
$found = true;
$proficiency = $usercompetency->get('proficiency');
}
}
if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
if (!$proficiency) {
return false;
}
} else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
if ($proficiency) {
return true;
}
}
}
return $overall;
}
/**
* Returns array with sql code and parameters returning all ids
* of users who meet this particular criterion.
*
* @return array list($join, $where, $params)
*/
public function get_completed_criteria_sql() {
global $DB;
$join = '';
$where = '';
$params = [];
$competencyids = [];
$badge = $DB->get_record('badge', array('id' => $this->badgeid));
if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
// User has received ANY of the required competencies (we can use an in or equals list).
foreach ($this->params as $param) {
$competencyids[] = $param['competency'];
}
$where = ' AND uc2.competencyid ';
list($sql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED, 'usercomp');
$where .= $sql;
if ($badge->type == BADGE_TYPE_SITE) {
$join = ' JOIN {competency_usercomp} uc2 ON uc2.userid = u.id';
} else if ($badge->type == BADGE_TYPE_COURSE) {
$join = ' JOIN {competency_usercompcourse} uc2 ON uc2.userid = u.id AND uc2.courseid = :competencycourseid ';
$params['competencycourseid'] = $badge->courseid;
}
$where .= ' AND uc2.proficiency = :isproficient ';
$params['isproficient'] = true;
return array($join, $where, $params);
} else {
// User has received ALL of the required competencies (we have to join on each one).
$joincount = 0;
foreach ($this->params as $param) {
$joincount++;
$join .= ' JOIN {competency_usercomp} uc' . $joincount . ' ON uc' . $joincount . '.userid = u.id';
$where .= ' AND uc' . $joincount . '.competencyid = :competencyindex' . $joincount;
$params['competencyindex' . $joincount] = $param['competency'];
$where .= ' AND uc' . $joincount . '.userid = u.id';
$where .= ' AND uc' . $joincount . '.proficiency = :isproficient' . $joincount;
$params['isproficient' . $joincount] = true;
}
return array($join, $where, $params);
}
return array($join, $where, $params);
}
}

View File

@ -78,7 +78,7 @@ class edit_criteria_form extends moodleform {
$errors = parent::validation($data, $files);
$addcourse = $this->_customdata['addcourse'];
if (!$addcourse && isset($this->_customdata['criteria']->required_param)) {
if (!$addcourse && isset($this->_customdata['criteria']->required_param) && !isset($this->_customdata['criteria']->self_validation)) {
$required = $this->_customdata['criteria']->required_param;
$pattern1 = '/^' . $required . '_(\d+)$/';
$pattern2 = '/^' . $required . '_(\w+)$/';

View File

@ -0,0 +1,70 @@
{{!
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/>.
}}
{{!
@template core_badges/award_criteria_competencies
List of competencies for a badge.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* competencies array
* id int
* shortname string
* idnumber string
Example context (json):
{
"competencies":
[
{
"id": 1,
"shortname": "Competency",
"idnumber": "C1"
}
]
}
}}
<div data-region="competencies">
<ul>
{{#competencies}}
<li>
<div class="pull-right">
<a href="#" data-action="deselect-competency" data-id="{{id}}">{{#pix}}t/delete, core, {{#str}}delete{{/str}}{{/pix}}</a>
</div>
<p>
<a href="#" data-action="competency-dialogue" data-id="{{id}}">
{{{shortname}}}{{#idnumber}} {{idnumber}}{{/idnumber}}
</a>
</p>
</li>
{{/competencies}}
</ul>
{{^competencies}}
{{#str}}nocompetencies, core_badges{{/str}}
{{/competencies}}
</div>
{{#js}}
require(['tool_lp/competencydialogue'], function(Compdialogue) {
Compdialogue.init();
});
{{/js}}

View File

@ -46,7 +46,7 @@ Feature: Add badges to the system
And I should see "Test badge with 'apostrophe' and other friends (&@#)"
And I should see "Endorsement"
And I should see "Related badges (0)"
And I should see "External alignments (0)"
And I should see "Alignments (0)"
And I should not see "Create badge"
And I follow "Manage badges"
And I should see "Number of badges available: 1"
@ -118,7 +118,7 @@ Feature: Add badges to the system
Then I should see "Changes saved"
@javascript @_file_upload
Scenario: External alignments for Badge
Scenario: Alignments for Badge
Given I navigate to "Badges > Add a new badge" in site administration
And I set the following fields to these values:
| Name | Test Badge |
@ -133,15 +133,15 @@ Feature: Add badges to the system
When I press "Create badge"
Then I should see "Test Badge"
And I should see "Endorsement"
And I follow "External alignments (0)"
And I should see "This badge does not have any external alignments specified."
And I press "Add external alignment"
And I follow "Alignments (0)"
And I should see "This badge does not have any external skills or standards specified."
And I press "Add external skill or standard"
And I set the following fields to these values:
| External alignment name | Test Badge Alignments |
| Name | Test Badge Alignments |
| URL | https://alignments.example.com |
| Description | Test Badge Alignments description |
When I press "Save changes"
And I should see "External alignments (1)"
And I should see "Alignments (1)"
@javascript @_file_upload
Scenario: Add a badge from Site badges section
@ -169,7 +169,7 @@ Feature: Add badges to the system
And I should see "Test badge with 'apostrophe' and other friends (&@#) 2"
And I should see "Endorsement"
And I should see "Related badges (0)"
And I should see "Competencies (0)"
And I should see "Alignments (0)"
And I should not see "Create badge"
And I follow "Manage badges"
And I should see "Number of badges available: 1"

View File

@ -0,0 +1,119 @@
@core @core_badges @_file_upload
Feature: Award badges based on competency completion
In order to award badges to users based on competency completion
As an admin
I need to add competency completion criteria to badges in the system
Background: Setup the competency framework and the course
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | First | User | first@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| user1 | C1 | student |
And the following lp "frameworks" exist:
| shortname | idnumber |
| Framework 1 | sc-y-2 |
And the following lp "competencies" exist:
| shortname | framework |
| comp1 | sc-y-2 |
And I log in as "admin"
@javascript
Scenario: Award badge for completing a competency in a course
# Add a competency to the course
When I am on "Course 1" course homepage
And I follow "Competencies"
And I press "Add competencies to course"
And "Competency picker" "dialogue" should be visible
And I select "comp1" of the competency tree
And I click on "Add" "button" in the "Competency picker" "dialogue"
And I click on "Edit" "link" in the "[data-region='configurecoursecompetencies']" "css_element"
And I click on "Rating a competency only updates the competency in this course" "text"
And I click on "Save changes" "button" in the "Configure course competencies" "dialogue"
# Add a badge to the course
And I am on "Course 1" course homepage
And I navigate to "Badges > Add a new badge" in current page administration
And I follow "Add a new badge"
And I set the following fields to these values:
| Name | Course Badge |
| Description | Course badge description |
| issuername | Tester of course badge |
And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
And I press "Create badge"
# Set the competency as a criteria for the badge
And I set the field "type" to "Competencies"
And I press "Add competency"
And "Competency picker" "dialogue" should be visible
And I select "comp1" of the competency tree
And I click on "Add" "button" in the "Competency picker" "dialogue"
# And I wait "1" seconds
And I wait until the page is ready
And I press "Save"
# And I wait "1" seconds
And I wait until the page is ready
# Enable the badge
And I press "Enable access"
And I press "Continue"
# Rate the competency in the course
And I am on "Course 1" course homepage
And I follow "Competencies"
And I click on "comp1" "link" in the "[data-region='coursecompetencies']" "css_element"
And I press "Rate"
And I set the following fields to these values:
| Rating | C |
And I click on "Rate" "button" in the "Rate" "dialogue"
And I log out
# See if we got the badge
Then I log in as "user1"
And I follow "Profile" in the user menu
And I should see "Course Badge"
@javascript
Scenario: Award badge for completing a competency in the site
# Add a competency to the course
When I am on "Course 1" course homepage
And I follow "Competencies"
And I press "Add competencies to course"
And "Competency picker" "dialogue" should be visible
And I select "comp1" of the competency tree
And I click on "Add" "button" in the "Competency picker" "dialogue"
# Add a badge to the site
And I navigate to "Badges > Add a new badge" in site administration
And I set the following fields to these values:
| Name | Site Badge |
| Description | Site badge description |
| issuername | Tester of site badge |
And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
And I press "Create badge"
# Set the competency as a criteria for the badge
And I set the field "type" to "Competencies"
And I press "Add competency"
And "Competency picker" "dialogue" should be visible
And I select "comp1" of the competency tree
And I click on "Add" "button" in the "Competency picker" "dialogue"
# And I wait "1" seconds
And I wait until the page is ready
And I press "Save"
# Enable the badge
# And I wait "1" seconds
And I wait until the page is ready
And I press "Enable access"
And I press "Continue"
# Rate the competency in the course
And I am on "Course 1" course homepage
And I follow "Competencies"
And I click on "comp1" "link" in the "[data-region='coursecompetencies']" "css_element"
And I press "Rate"
And I set the following fields to these values:
| Rating | C |
And I wait until the page is ready
And I click on "Rate" "button" in the "Rate" "dialogue"
And I log out
# See if we got the badge
Then I log in as "user1"
And I follow "Profile" in the user menu
And I should see "Site Badge"

View File

@ -27,9 +27,11 @@
$string['actions'] = 'Actions';
$string['activate'] = 'Enable access';
$string['activatesuccess'] = 'Access to the badges was successfully enabled.';
$string['addalignment'] = 'Add competency';
$string['addalignment'] = 'Add external skill or standard';
$string['addbadge'] = 'Add badges';
$string['addbadge_help'] = 'Select all badges that should be added to this badge requirement. Hold CTRL key to select multiple items.';
$string['addcompetency'] = 'Add competency';
$string['addcompetency_help'] = 'Select all competencies that should be added to this badge requirement. Hold CTRL key to select multiple items.';
$string['addbadgecriteria'] = 'Add badge criteria';
$string['addcriteria'] = 'Add criteria';
$string['addcriteriatext'] = 'To start adding criteria, please select one of the options from the drop-down menu.';
@ -42,12 +44,13 @@ $string['addtobackpack'] = 'Add to backpack';
$string['adminonly'] = 'This page is restricted to site administrators only.';
$string['after'] = 'after the date of issue.';
$string['aggregationmethod'] = 'Aggregation method';
$string['alignment'] = 'Competency';
$string['alignment'] = 'Alignment';
$string['all'] = 'All';
$string['allmethod'] = 'All of the selected conditions are met';
$string['allmethodactivity'] = 'All of the selected activities are complete';
$string['allmethodbadges'] = 'All of the selected badges have been earned';
$string['allmethodcohort'] = 'Membership in all the selected cohorts';
$string['allmethodcompetencies'] = 'All of the selected competencies have been completed';
$string['allmethodcourseset'] = 'All of the selected courses are complete';
$string['allmethodmanual'] = 'All of the selected roles award the badge';
$string['allmethodprofile'] = 'All of the selected profile fields have been completed';
@ -62,6 +65,7 @@ $string['anymethod'] = 'Any of the selected conditions is met';
$string['anymethodactivity'] = 'Any of the selected activities is complete';
$string['anymethodbadges'] = 'Any of the selected badges have been earned';
$string['anymethodcohort'] = 'Membership in any of the selected cohorts';
$string['anymethodcompetencies'] = 'Any of the selected competencies have been completed';
$string['anymethodcourseset'] = 'Any of the selected courses is complete';
$string['anymethodmanual'] = 'Any of the selected roles awards the badge';
$string['anymethodprofile'] = 'Any of the selected profile fields has been completed';
@ -157,7 +161,7 @@ $string['bendorsement'] = 'Endorsement';
$string['bmessage'] = 'Message';
$string['boverview'] = 'Overview';
$string['brelated'] = 'Related badges ({$a})';
$string['balignment'] = 'Competencies ({$a})';
$string['balignment'] = 'Alignments ({$a})';
$string['bydate'] = ' complete by';
$string['imagecaption'] = 'Image caption';
$string['imagecaption_help'] = 'If specified, an image caption is displayed on the badge page.';
@ -197,6 +201,7 @@ $string['criteria_descr_short5'] = 'Complete <strong>{$a}</strong> of: ';
$string['criteria_descr_short6'] = 'Complete <strong>{$a}</strong> of: ';
$string['criteria_descr_short7'] = 'Complete <strong>{$a}</strong> of: ';
$string['criteria_descr_short8'] = 'Cohort membership in <strong>{$a}</strong> of: ';
$string['criteria_descr_short9'] = 'Complete <strong>{$a}</strong> of: ';
$string['criteria_descr_single_short1'] = 'Complete: ';
$string['criteria_descr_single_short2'] = 'Awarded by: ';
$string['criteria_descr_single_short4'] = 'Complete the course ';
@ -204,6 +209,7 @@ $string['criteria_descr_single_short5'] = 'Complete: ';
$string['criteria_descr_single_short6'] = 'Complete: ';
$string['criteria_descr_single_short7'] = 'Complete: ';
$string['criteria_descr_single_short8'] = 'Membership in: ';
$string['criteria_descr_single_short9'] = 'Complete: ';
$string['criteria_descr_single_1'] = 'The following activity has to be completed:';
$string['criteria_descr_single_2'] = 'This badge has to be awarded by a user with the following role:';
$string['criteria_descr_single_4'] = 'Users must complete the course';
@ -211,6 +217,7 @@ $string['criteria_descr_single_5'] = 'The following course has to be completed:'
$string['criteria_descr_single_6'] = 'The following user profile field has to be completed:';
$string['criteria_descr_single_7'] = 'The following badge has to be earned:';
$string['criteria_descr_single_8'] = 'Membership in the following cohort is required:';
$string['criteria_descr_single_9'] = 'The following competencies have to be completed:';
$string['criteria_descr_0'] = 'Users are awarded this badge when they complete <strong>{$a}</strong> of the listed requirements.';
$string['criteria_descr_1'] = '<strong>{$a}</strong> of the following activities are completed:';
$string['criteria_descr_2'] = 'This badge has to be awarded by the users with <strong>{$a}</strong> of the following roles:';
@ -219,6 +226,7 @@ $string['criteria_descr_5'] = '<strong>{$a}</strong> of the following courses ha
$string['criteria_descr_6'] = '<strong>{$a}</strong> of the following user profile fields have to be completed:';
$string['criteria_descr_7'] = '<strong>{$a}</strong> of the following badges have to be earned:';
$string['criteria_descr_8'] = 'Membership in <strong>{$a}</strong> of the following cohorts is required:';
$string['criteria_descr_9'] = '<strong>{$a}</strong> of the following competencies have to be completed:';
$string['criteria_0'] = 'This badge is awarded when...';
$string['criteria_1'] = 'Activity completion';
$string['criteria_1_help'] = 'Allows a badge to be awarded to users based on the completion of a set of activities within a course.';
@ -236,6 +244,8 @@ $string['criteria_7'] = 'Awarded badges';
$string['criteria_7_help'] = 'Allows a badge to be awarded to users based on the other badges thay have earned.';
$string['criteria_8'] = 'Cohort membership';
$string['criteria_8_help'] = 'Allows a badge to be awarded to users based on cohort membership.';
$string['criteria_9'] = 'Competencies';
$string['criteria_9_help'] = 'Allows a badge to be awarded to users based on the competencies thay have completed.';
$string['criterror'] = 'Current parameters issues';
$string['criterror_help'] = 'This fieldset shows all parameters that were initially added to this badge requirement but are no longer available. It is recommended that you un-check such parameters to make sure that users can earn this badge in the future.';
$string['currentimage'] = 'Current image';
@ -379,12 +389,13 @@ $string['mybackpack'] = 'My backpack settings';
$string['never'] = 'Never';
$string['newbadge'] = 'Add a new badge';
$string['newimage'] = 'New image';
$string['noalignment'] = 'This badge does not have any competencies specified.';
$string['noalignment'] = 'This badge does not have any external skills or standards specified.';
$string['noawards'] = 'This badge has not been earned yet.';
$string['nobackpack'] = 'There is no backpack service connected to this account.<br/>';
$string['nobackpackbadges'] = 'There are no badges in the collections you have selected. <a href="mybackpack.php">Add more collections</a>.';
$string['nobackpackcollections'] = 'No badge collections have been selected. <a href="mybackpack.php">Add collections</a>.';
$string['nobadges'] = 'There are no badges available.';
$string['nocompetencies'] = 'No competencies selected.';
$string['nocriteria'] = 'Criteria for this badge have not been set up yet.';
$string['noendorsement'] = 'This badge does not have an endorsement.';
$string['noexpiry'] = 'This badge does not have an expiry date.';
@ -393,7 +404,7 @@ $string['norelated'] = 'This badge does not have any related badges.';
$string['notacceptedrole'] = 'Your current role assignment is not among the roles that can manually issue this badge.<br/>
If you would like to see users who have already earned this badge, you can visit {$a} page. ';
$string['notconnected'] = 'Not connected';
$string['notealignment'] = 'External competencies, skills or standards which the badge covers may be specified. Any competencies are displayed on the badge page.';
$string['notealignment'] = 'External skills or standards, which the badge is aligned with, may be specified. Any external skills or standards are displayed on the badge page.';
$string['noteendorsement'] = 'An endorsement from a third party may be used to add value to the badge. For example, a badge issued by a teacher may be endorsed by the school, or a badge issued by a local awarding body may be endorsed by the national awarding body.';
$string['noterelated'] = 'Badges with a connection may be marked as related. For example, badges with the same criteria which are displayed in different languages may be marked as related. Any related badges are displayed on the badge page.';
$string['nothingtoadd'] = 'There are no available criteria to add.';
@ -495,16 +506,16 @@ Once a badge has been issued to at least one user, it automatically becomes **LO
We want to make sure that all users complete the same requirements to earn a badge. Currently, it is not possible to revoke badges. If we allowed badges requirements to be modified all the time, we would most likely end up with users having the same badge for meeting completely different requirements.';
$string['subject'] = 'Message subject';
$string['targetname'] = 'Competency name';
$string['targetname_help'] = 'The competency, skill or standard which the badge covers.';
$string['targetname'] = 'Name';
$string['targetname_help'] = 'The external skill or standard which the badge is aligned with.';
$string['targeturl'] = 'URL';
$string['targeturl_help'] = 'A link to a page describing the competency, skill or standard. The URL should have a prefix http:// or https://.';
$string['targeturl_help'] = 'A link to a page describing the external skill or standard. The URL should have a prefix http:// or https://.';
$string['targetdescription'] = 'Description';
$string['targetdescription_help'] = 'Short description of the alignment target.';
$string['targetdescription_help'] = 'Short description of the external skill or standard.';
$string['targetframework'] = 'Framework';
$string['targetframework_help'] = 'The name of the competency framework.';
$string['targetframework_help'] = 'The name of the external skill or standard framework.';
$string['targetcode'] = 'Code';
$string['targetcode_help'] = 'A unique string identifier for referencing the competency within its framework.';
$string['targetcode_help'] = 'A unique string identifier for referencing the external skill or standard within its framework.';
$string['type'] = 'Type';
$string['variablesubstitution'] = 'Variable substitution in messages.';
$string['variablesubstitution_help'] = 'In a badge message, certain variables can be inserted into the subject and/or body of a message so that they will be replaced with real values when the message is sent. The variables should be inserted into the text exactly as they are shown below. The following variables can be used:

View File

@ -226,7 +226,8 @@ class badge {
BADGE_CRITERIA_TYPE_MANUAL,
BADGE_CRITERIA_TYPE_COURSE,
BADGE_CRITERIA_TYPE_BADGE,
BADGE_CRITERIA_TYPE_ACTIVITY
BADGE_CRITERIA_TYPE_ACTIVITY,
BADGE_CRITERIA_TYPE_COMPETENCY
);
} else if ($this->type == BADGE_TYPE_SITE) {
$criteriatypes = array(
@ -236,6 +237,7 @@ class badge {
BADGE_CRITERIA_TYPE_BADGE,
BADGE_CRITERIA_TYPE_PROFILE,
BADGE_CRITERIA_TYPE_COHORT,
BADGE_CRITERIA_TYPE_COMPETENCY
);
}
@ -508,6 +510,7 @@ class badge {
}
list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql();
// For site level badges, get all active site users who can earn this badge and haven't got it yet.
if ($this->type == BADGE_TYPE_SITE) {
$sql = "SELECT DISTINCT u.id, bi.badgeid
@ -838,7 +841,7 @@ class badge {
*
* @return array List content alignments.
*/
public function get_alignment() {
public function get_alignments() {
global $DB;
return $DB->get_records('badge_alignment', array('badgeid' => $this->id));
}

View File

@ -62,7 +62,10 @@ $observers = array(
'eventname' => '\core\event\cohort_member_added',
'callback' => 'core_badges_observer::cohort_criteria_review',
),
array(
'eventname' => '\core\event\competency_evidence_created',
'callback' => 'core_badges_observer::competency_criteria_review',
),
// Competencies.
array(
'eventname' => '\core\event\course_completed',