Merge branch 'MDL-63876-master' of git://github.com/damyon/moodle

This commit is contained in:
Jun Pataleta
2019-03-29 11:45:29 +08:00
48 changed files with 1290 additions and 236 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
define(["core/ajax","core/notification","core/templates","tool_lp/tree","tool_lp/competency_outcomes","jquery"],function(a,b,c,d,e,f){var g={},h=0,i="",j="",k="",l=!1,m=function(a,b){var c=0,d=!1;for(a.haschildren=!1,a.children=[],c=0;c<b.length;c++)d=b[c],d.parentid==a.id&&(a.haschildren=!0,a.children.push(d),m(d,b))},n=function(b){var e=f.Deferred();return c.render("tool_lp/loading",{}).done(function(n,o){c.replaceNodeContents(f(j),n,o);var p=a.call([{methodname:"core_competency_search_competencies",args:{searchtext:b,competencyframeworkid:h}}]);p[0].done(function(a){g={};var b=0;for(b=0;b<a.length;b++)g[a[b].id]=a[b];var h=[],n=!1;for(b=0;b<a.length;b++)n=a[b],0===parseInt(n.parentid,10)&&(h.push(n),m(n,a));var o={shortname:i,canmanage:l,competencies:h};c.render("tool_lp/competencies_tree_root",o).done(function(a,b){c.replaceNodeContents(f(j),f(a).html(),b);var h=new d(j,(!1));if(k){var i=f(j).find("[data-id="+k+"]");i.length&&(h.selectItem(i),h.updateFocus(i))}e.resolve(g)}).fail(e.reject)}).fail(e.reject)}),e.promise()},o=function(a,b){var c=b.selected;k=c.attr("data-id")};return{init:function(a,c,d,e,f){h=a,i=c,l=f,j=e,n(d).fail(b.exception),this.on("selectionchanged",o)},on:function(a,b){f(j).on(a,b)},getChildren:function(a){var b=[];return f.each(g,function(c,d){d.parentid==a&&b.push(d)}),b},getCompetencyFrameworkId:function(){return h},getCompetency:function(a){return g[a]},getCompetencyLevel:function(a){var b=this.getCompetency(a),c=b.path.replace(/^\/|\/$/g,"").split("/").length;return c},hasChildren:function(a){return this.getChildren(a).length>0},hasRule:function(a){var b=this.getCompetency(a);return!!b&&(b.ruleoutcome!=e.OUTCOME_NONE&&b.ruletype)},reloadCompetencies:function(){return n("").fail(b.exception)},listCompetencies:function(){return g}}});
define(["core/ajax","core/notification","core/templates","tool_lp/tree","tool_lp/competency_outcomes","jquery"],function(a,b,c,d,e,f){var g={},h=0,i="",j="",k="",l=!1,m=function(a,b){var c=0,d=!1;for(a.haschildren=!1,a.children=[],c=0;c<b.length;c++)d=b[c],d.parentid==a.id&&(a.haschildren=!0,a.children.push(d),m(d,b))},n=function(b){var e=f.Deferred();return c.render("tool_lp/loading",{}).done(function(n,o){c.replaceNodeContents(f(j),n,o);var p=a.call([{methodname:"core_competency_search_competencies",args:{searchtext:b,competencyframeworkid:h}}]);p[0].done(function(a){g={};var b=0;for(b=0;b<a.length;b++)g[a[b].id]=a[b];var h=[],n=!1;for(b=0;b<a.length;b++)n=a[b],0===parseInt(n.parentid,10)&&(h.push(n),m(n,a));var o={shortname:i,canmanage:l,competencies:h};c.render("tool_lp/competencies_tree_root",o).done(function(a,b){c.replaceNodeContents(f(j),f(a).html(),b);var h=new d(j,(!1));if(k){var i=f(j).find("[data-id="+k+"]");i.length&&(h.selectItem(i),h.updateFocus(i))}e.resolve(g)}).fail(e.reject)}).fail(e.reject)}),e.promise()},o=function(a,b){var c=b.selected;k=c.attr("data-id")};return{init:function(a,c,d,e,f,g){h=a,i=c,l=f,j=e,n(d).fail(b.exception),g>0&&(k=g),this.on("selectionchanged",o)},on:function(a,b){f(j).on(a,b)},getChildren:function(a){var b=[];return f.each(g,function(c,d){d.parentid==a&&b.push(d)}),b},getCompetencyFrameworkId:function(){return h},getCompetency:function(a){return g[a]},getCompetencyLevel:function(a){var b=this.getCompetency(a),c=b.path.replace(/^\/|\/$/g,"").split("/").length;return c},hasChildren:function(a){return this.getChildren(a).length>0},hasRule:function(a){var b=this.getCompetency(a);return!!b&&(b.ruleoutcome!=e.OUTCOME_NONE&&b.ruletype)},reloadCompetencies:function(){return n("").fail(b.exception)},listCompetencies:function(){return g}}});
@@ -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("tool_lp/form_competency_list",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)}}});
@@ -672,6 +672,7 @@ define(['jquery',
context.showdeleterelatedaction = true;
context.showrelatedcompetencies = true;
context.showrule = false;
context.pluginbaseurl = url.relativeUrl('/admin/tool/lp');
if (competency.ruleoutcome != Outcomes.NONE) {
// Get the outcome and rule name.
+6 -2
View File
@@ -136,18 +136,22 @@ define(['core/ajax', 'core/notification', 'core/templates', 'tool_lp/tree', 'too
/**
* Initialise the tree.
*
* @param {Number} id The competency id.
* @param {Number} id The competency framework id.
* @param {String} shortname The framework shortname
* @param {String} search The current search string
* @param {String} selector The selector for the tree div
* @param {Boolean} canmanage Can manage the competencies
* @param {Number} competencyid The id of the competency to show first
*/
init: function(id, shortname, search, selector, canmanage) {
init: function(id, shortname, search, selector, canmanage, competencyid) {
competencyFrameworkId = id;
competencyFrameworkShortName = shortname;
competencyFramworkCanManage = canmanage;
treeSelector = selector;
loadCompetencies(search).fail(notification.exception);
if (competencyid > 0) {
currentNodeId = competencyid;
}
this.on('selectionchanged', rememberCurrent);
},
@@ -0,0 +1,138 @@
// 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 tool_lp/form_competency_element
* @package tool_lp
* @copyright 2019 Damyon Wiese <[email protected]>
* @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
* @return {boolean}
*/
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('tool_lp/form_competency_list', context);
}).then(function(html, js) {
Templates.replaceNode($('[data-region="competencies"]'), html, js);
return true;
}).fail(Notification.exception);
return true;
};
/**
* Deselect a competency
*
* @method unpickCompetenciesHandler
* @param {Event} e
* @return {boolean}
*/
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:tool_lp/form_competency_element */ {
/**
* 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);
}
};
});
+1 -1
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);
@@ -76,6 +76,9 @@ class competency_path_exporter extends \core\external\exporter {
],
'pagecontextid' => [
'type' => PARAM_INT
],
'showlinks' => [
'type' => PARAM_BOOL
]
];
}
@@ -91,6 +94,7 @@ class competency_path_exporter extends \core\external\exporter {
$ancestors = [];
$nodescount = count($this->related['ancestors']);
$i = 1;
$result->showlinks = \core_competency\api::show_links();
foreach ($this->related['ancestors'] as $competency) {
$exporter = new path_node_exporter([
'id' => $competency->get('id'),
@@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
use context_course;
use renderer_base;
use stdClass;
use moodle_url;
use core_competency\competency_framework;
use core_competency\external\competency_exporter;
use core_competency\external\competency_framework_exporter;
@@ -82,7 +83,10 @@ class competency_summary_exporter extends \core\external\exporter {
),
'comppath' => array(
'type' => competency_path_exporter::read_properties_definition(),
)
),
'pluginbaseurl' => [
'type' => PARAM_URL
]
);
}
@@ -136,6 +140,8 @@ class competency_summary_exporter extends \core\external\exporter {
'context' => $context
]);
$result->comppath = $exporter->export($output);
$result->pluginbaseurl = (new moodle_url('/admin/tool/lp'))->out(true);
$result->showlinks = \core_competency\api::show_links();
return (array) $result;
}
@@ -62,17 +62,22 @@ class manage_competencies_page implements renderable, templatable {
/** @var context $pagecontext The page context. */
protected $pagecontext = null;
/** @var \core_competency\competency $competency The competency to show when the page loads. */
protected $competency = null;
/**
* Construct this renderable.
*
* @param \core_competency\competency_framework $framework Competency framework.
* @param string $search Search string.
* @param context $pagecontext The page context.
* @param \core_competency\competency $competency The core competency to show when the page loads.
*/
public function __construct($framework, $search, $pagecontext) {
public function __construct($framework, $search, $pagecontext, $competency) {
$this->framework = $framework;
$this->pagecontext = $pagecontext;
$this->search = $search;
$this->competency = $competency;
$addpage = new single_button(
new moodle_url('/admin/tool/lp/editcompetencyframework.php'),
get_string('addnewcompetency', 'tool_lp')
@@ -97,6 +102,11 @@ class manage_competencies_page implements renderable, templatable {
$data->pagecontextid = $this->pagecontext->id;
$data->pluginbaseurl = (new moodle_url('/admin/tool/lp'))->out(true);
$data->competencyid = 0;
if ($this->competency) {
$data->competencyid = $this->competency->get('id');
}
$rulesmodules = array();
$rules = competency::get_available_rules();
foreach ($rules as $type => $rulename) {
@@ -0,0 +1,98 @@
<?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/>.
/**
* Site competencies element.
*
* @package tool_lp
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/form/hidden.php');
/**
* Site competencies element.
*
* @package tool_lp
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_lp_site_competencies_form_element extends MoodleQuickForm_hidden {
/**
* Constructor
*
* @param string $elementname Element name.
* @param string $value The element value.
* @param mixed $attributes Either a typical HTML attribute string or an associative array.
*/
public function __construct($elementname=null, $value='', $attributes=null) {
if ($elementname == null) {
// This is broken quickforms messing with the constructors.
return;
}
$attributes = array_merge(['data-action' => 'competencies'], $attributes ? $attributes : []);
parent::__construct($elementname, $value, $attributes);
$this->setType('hidden');
}
/**
* Generate the hidden field and the controls to show and pick the competencies.
*/
public function toHtml() {
global $PAGE;
$html = parent::toHTML();
if (!$this->isFrozen()) {
$context = context_system::instance();
$params = [$context->id];
// Require some JS to select the competencies.
$PAGE->requires->js_call_amd('tool_lp/form_competency_element', 'init', $params);
$html .= '<div class="form-group row">';
$html .= '<div class="col-md-3"></div>';
$html .= '<div class="col-md-9">';
$html .= '<div data-region="competencies"></div>';
$html .= '<div class="mt-3">';
$html .= '<a class="btn btn-secondary" role="button" data-action="select-competencies">';
$html .= get_string('addcompetency', 'tool_lp');
$html .= '</a>';
$html .= '</div>';
$html .= '</div>';
$html .= '</div>';
}
return $html;
}
/**
* Accepts a renderer
*
* @param HTML_QuickForm_Renderer $renderer the renderer for the element.
* @param boolean $required not used.
* @param string $error not used.
* @return void
*/
public function accept(&$renderer, $required=false, $error=null) {
$renderer->renderElement($this, false, '');
}
}
+13 -4
View File
@@ -25,14 +25,23 @@
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);
$competencyid = optional_param('competencyid', 0, PARAM_INT);
$competency = null;
if ($competencyid) {
$competency = \core_competency\api::read_competency($competencyid);
$id = $competency->get('competencyframeworkid');
$pagecontext = $competency->get_context();
$pagecontextid = $pagecontext->id; // Reference to the context we came from.
} else {
$id = required_param('competencyframeworkid', PARAM_INT);
$pagecontextid = required_param('pagecontextid', PARAM_INT); // Reference to the context we came from.
$pagecontext = context::instance_by_id($pagecontextid);
}
require_login();
\core_competency\api::require_enabled();
$pagecontext = context::instance_by_id($pagecontextid);
$framework = \core_competency\api::read_framework($id);
$context = $framework->get_context();
@@ -58,7 +67,7 @@ $PAGE->set_heading($title);
$output = $PAGE->get_renderer('tool_lp');
echo $output->header();
$page = new \tool_lp\output\manage_competencies_page($framework, $search, $pagecontext);
$page = new \tool_lp\output\manage_competencies_page($framework, $search, $pagecontext, $competency);
echo $output->render($page);
// Log the framework viewed event after rendering the page.
+1
View File
@@ -148,6 +148,7 @@ $string['nocompetenciesincourse'] = 'No competencies have been linked to this co
$string['nocompetenciesinevidence'] = 'No competencies have been linked to this evidence.';
$string['nocompetenciesinlearningplan'] = 'No competencies have been linked to this learning plan.';
$string['nocompetenciesintemplate'] = 'No competencies have been linked to this learning plan template.';
$string['nocompetenciesinlist'] = 'No competencies have been selected.';
$string['nocompetencyframeworks'] = 'No competency frameworks have been created yet.';
$string['nocompetencyselected'] = 'No competency selected';
$string['nocrossreferencedcompetencies'] = 'No other competencies have been cross-referenced to this competency.';
+68
View File
@@ -217,3 +217,71 @@ function tool_lp_get_fontawesome_icon_map() {
'tool_lp:url' => 'fa-external-link'
];
}
/**
* Render a short bit of information about a competency.
*
* @param \core_competency\competency $competency The competency to show.
* @param \core_competency\competency_framework $framework The competency framework.
* @param boolean $includerelated If true, show related competencies.
* @param boolean $includecourses If true, show courses using this competency.
* @param boolean $skipenabled If true, show this competency even if competencies are disabled.
* @return string The html summary for the competency.
*/
function tool_lp_render_competency_summary(\core_competency\competency $competency,
\core_competency\competency_framework $framework,
$includerelated,
$includecourses,
$skipenabled = false) {
global $PAGE;
if (!$skipenabled && !get_config('core_competency', 'enabled')) {
return;
}
$summary = new \tool_lp\output\competency_summary($competency, $framework, $includerelated, $includecourses);
$output = $PAGE->get_renderer('tool_lp');
return $output->render($summary);
}
/**
* Inject a course competency picker into the form.
*
* @param MoodleQuickForm $mform The actual form object (required to modify the form).
* @param integer $courseid - SITEID or a course id
* @param context $context - The page context
* @param string $elementname - The name of the form element to create
*/
function tool_lp_competency_picker($mform, $courseid, $context, $elementname) {
global $CFG, $COURSE;
if (!get_config('core_competency', 'enabled')) {
return;
}
if ($courseid == SITEID) {
if (!has_capability('moodle/competency:competencymanage', $context)) {
return;
}
MoodleQuickForm::registerElementType('site_competencies',
"$CFG->dirroot/$CFG->admin/tool/lp/classes/site_competencies_form_element.php",
'tool_lp_site_competencies_form_element');
$mform->addElement('site_competencies', $elementname);
} else {
if (!has_capability('moodle/competency:coursecompetencymanage', $context)) {
return;
}
MoodleQuickForm::registerElementType('course_competencies',
"$CFG->dirroot/$CFG->admin/tool/lp/classes/course_competencies_form_element.php",
'tool_lp_course_competencies_form_element');
$options = array(
'courseid' => $COURSE->id
);
$mform->addElement('course_competencies', $elementname, get_string('modcompetencies', 'tool_lp'), $options);
}
$mform->setType($elementname, PARAM_SEQUENCE);
}
+11 -3
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%;
}
@@ -56,10 +56,24 @@
}}
<nav id="competency-path-{{uniqid}}">
<small>
<a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{framework.id}}&pagecontextid={{pagecontextid}}" >{{{framework.name}}}</a>
{{#showlinks}}
<a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{framework.id}}&pagecontextid={{pagecontextid}}" >
{{{framework.name}}}
</a>
{{/showlinks}}
{{^showlinks}}
{{{framework.name}}}
{{/showlinks}}
/
{{#ancestors}}
<a data-action="competency-dialogue" href="#" data-id="{{id}}">{{{name}}}</a>
{{#showlinks}}
<a href="{{pluginbaseurl}}/competencies.php?competencyid={{id}}">
{{{name}}}
</a>
{{/showlinks}}
{{^showlinks}}
{{{name}}}
{{/showlinks}}
{{^last}}<span> / </span>{{/last}}
{{/ancestors}}
</small>
@@ -50,7 +50,14 @@
}}
<div class='competency-heading'>
<h4 id="competency_link_{{competency.id}}">{{{competency.shortname}}}
<small>{{competency.idnumber}}</small>
<small>
{{#showlinks}}
<a href="{{pluginbaseurl}}/competencies.php?competencyid={{competency.id}}">{{competency.idnumber}}</a>
{{/showlinks}}
{{^showlinks}}
{{competency.idnumber}}
{{/showlinks}}
</small>
</h4>
{{#framework}}
<div class='competency-origin'>
@@ -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 tool_lp/form_competency_list
List of competencies for a form element.
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}}nocompetenciesinlist, tool_lp{{/str}}
{{/competencies}}
</div>
{{#js}}
require(['tool_lp/competencydialogue'], function(Compdialogue) {
Compdialogue.init();
});
{{/js}}
@@ -15,6 +15,8 @@
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_lp/manage_competencies_page
Manage competencies template.
Classes required for JS:
@@ -25,10 +27,15 @@
* data-region=term
* data-region=selected-competency
Context variables required for this template:
* framework -
* competencies - array of objects containing id, shortname, idnumber, sortorder, parentid, competencyframeworkid, path
* canmanage - true if this user has permission to manage the competencies
Example context (json):
{
"framework": {
"description": "Framework",
"id": 0
},
"competencies": [],
"canmanage": false
}
}}
<div data-region="managecompetencies">
<h2>
@@ -135,7 +142,8 @@ require(['tool_lp/tree', 'tool_lp/competencytree', 'tool_lp/competencyactions',
{{#quote}} {{{framework.shortname}}} {{/quote}},
'{{search}}',
'[data-enhance=tree]',
{{canmanage}});
{{canmanage}},
{{competencyid}});
actions.init(treeModel, {{pagecontextid}}, '{{{framework.taxonomies}}}', {{{rulesmodules}}});
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* List competencies, skills, or standards are targeted by a BadgeClass.
* List alignments, skills, or standards are targeted by a BadgeClass.
*
* @package core
* @subpackage badges
@@ -24,7 +24,7 @@
*/
require_once(__DIR__ . '/../config.php');
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->dirroot . '/badges/competency_form.php');
require_once($CFG->dirroot . '/badges/alignment_form.php');
$badgeid = required_param('id', PARAM_INT);
$alignmentid = optional_param('alignmentid', 0, PARAM_INT);
@@ -53,7 +53,7 @@ if ($badge->type == BADGE_TYPE_COURSE) {
navigation_node::override_active_url($navurl, true);
}
$currenturl = new moodle_url('/badges/competency.php', array('id' => $badge->id));
$currenturl = new moodle_url('/badges/alignment.php', array('id' => $badge->id));
$PAGE->set_context($context);
$PAGE->set_url($currenturl);
$PAGE->set_heading($badge->name);
@@ -63,8 +63,8 @@ $PAGE->navbar->add($badge->name);
$output = $PAGE->get_renderer('core', 'badges');
$msg = optional_param('msg', '', PARAM_TEXT);
$emsg = optional_param('emsg', '', PARAM_TEXT);
$url = new moodle_url('/badges/competency.php', array('id' => $badge->id, 'action' => $action, 'alignmentid' => $alignmentid));
$mform = new competency_alignment_form($url, array('badge' => $badge, 'action' => $action, 'alignmentid' => $alignmentid));
$url = new moodle_url('/badges/alignment.php', array('id' => $badge->id, 'action' => $action, 'alignmentid' => $alignmentid));
$mform = new alignment_form($url, array('badge' => $badge, 'action' => $action, 'alignmentid' => $alignmentid));
if ($mform->is_cancelled()) {
redirect($currenturl);
} else if ($mform->is_submitted() && $mform->is_validated() && ($data = $mform->get_data())) {
@@ -94,12 +94,12 @@ if ($alignmentid || $action == 'add' || $action == 'edit') {
$mform->display();
} else if (empty($action)) {
if (!$badge->is_active() && !$badge->is_locked()) {
$urlbutton = new moodle_url('/badges/competency.php', array('id' => $badge->id, 'action' => 'add'));
$urlbutton = new moodle_url('/badges/alignment.php', array('id' => $badge->id, 'action' => 'add'));
echo $OUTPUT->box($OUTPUT->single_button($urlbutton, get_string('addalignment', 'badges')), 'clearfix mdl-align');
}
$alignment = $badge->get_alignment();
if (count($alignment) > 0) {
$renderrelated = new badge_competencies_alignment($alignment, $badgeid);
$alignments = $badge->get_alignments();
if (count($alignments) > 0) {
$renderrelated = new badge_alignments($alignments, $badgeid);
echo $output->render($renderrelated);
} else {
echo $output->notification(get_string('noalignment', 'badges'));
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Processing actions with competencies alignment.
* Processing actions with alignments.
*
* @package core
* @subpackage badges
@@ -30,7 +30,7 @@ $badgeid = required_param('id', PARAM_INT); // Badge ID.
$action = optional_param('action', 'remove', PARAM_TEXT); // Remove.
require_login();
$return = new moodle_url('/badges/competency.php', array('id' => $badgeid));
$return = new moodle_url('/badges/alignment.php', array('id' => $badgeid));
$badge = new badge($badgeid);
$context = $badge->get_context();
require_capability('moodle/badges:configuredetails', $context);
@@ -38,4 +38,4 @@ require_capability('moodle/badges:configuredetails', $context);
if ($action == 'remove') {
$badge->delete_alignment($alignmentid);
}
redirect($return);
redirect($return);
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Form competency for editing.
* Form alignment for editing.
*
* @package core
* @subpackage badges
@@ -27,13 +27,13 @@ require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/badgeslib.php');
/**
* Form to edit competency alignment.
* Form to edit alignment.
*
* @copyright 2018 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
*/
class competency_alignment_form extends moodleform {
class alignment_form extends moodleform {
/**
* Defines the form.
@@ -68,7 +68,7 @@ class competency_alignment_form extends moodleform {
$this->add_action_buttons();
if ($action == 'edit' || $alignmentid) {
$alignment = new stdClass();
$alignment = $DB->get_record_select('badge_competencies', 'id = ?', array($alignmentid));
$alignment = $DB->get_record_select('badge_alignment', 'id = ?', array($alignmentid));
$this->set_data($alignment);
// Freeze all elements if badge is active or locked.
if ($badge->is_active() || $badge->is_locked()) {
@@ -91,4 +91,4 @@ class competency_alignment_form extends moodleform {
}
return $errors;
}
}
}
+4 -4
View File
@@ -95,9 +95,9 @@ if ($badge->status != BADGE_STATUS_INACTIVE) {
$json['endorsement'] = $endorsementurl->out(false);
}
$competencies = $badge->get_alignment();
if (!empty($competencies)) {
foreach ($competencies as $item) {
$alignments = $badge->get_alignments();
if (!empty($alignments)) {
foreach ($alignments as $item) {
$alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
if ($item->targetdescription) {
$alignment['targetDescription'] = $item->targetdescription;
@@ -123,4 +123,4 @@ if ($badge->status != BADGE_STATUS_INACTIVE) {
$json['error'] = get_string('error:relatedbadgedoesntexist', 'badges');
}
echo $OUTPUT->header();
echo json_encode($json);
echo json_encode($json);
+6 -6
View File
@@ -223,15 +223,15 @@ class core_badges_assertion {
}
/**
* Get competencies alignment of the badge.
* Get alignment of the badge.
*
* @return array competencies information.
* @return array information.
*/
public function get_competencies_alignment() {
public function get_alignments() {
global $DB;
$badgeid = $this->_data->id;
$alignments = array();
$items = $DB->get_records_select('badge_competencies', 'badgeid = ?', array($badgeid));
$items = $DB->get_records_select('badge_alignment', 'badgeid = ?', array($badgeid));
foreach ($items as $item) {
$alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
if ($item->targetdescription) {
@@ -301,8 +301,8 @@ class core_badges_assertion {
$endorsementurl = new moodle_url('/badges/endorsement_json.php', array('id' => $this->_data->id));
$json['endorsement'] = $endorsementurl->out(false);
}
if ($competencies = $this->get_competencies_alignment()) {
$json['alignment'] = $competencies;
if ($alignments = $this->get_alignments()) {
$json['alignment'] = $alignments;
}
if ($this->_data->imageauthorname ||
$this->_data->imageauthoremail ||
+7 -7
View File
@@ -150,17 +150,17 @@ class core_badges_external extends external_api {
// Create a badge instance to be able to get the endorsement and other info.
$badgeinstance = new badge($badge->id);
$endorsement = $badgeinstance->get_endorsement();
$competencies = $badgeinstance->get_alignment();
$alignments = $badgeinstance->get_alignments();
$relatedbadges = $badgeinstance->get_related_badges();
if (!$canconfiguredetails) {
// Return only the properties visible by the user.
if (!empty($competencies)) {
foreach ($competencies as $competency) {
unset($competency->targetdescription);
unset($competency->targetframework);
unset($competency->targetcode);
if (!empty($alignments)) {
foreach ($alignments as $alignment) {
unset($alignment->targetdescription);
unset($alignment->targetframework);
unset($alignment->targetcode);
}
}
@@ -176,7 +176,7 @@ class core_badges_external extends external_api {
$related = array(
'context' => $context,
'endorsement' => $endorsement ? $endorsement : null,
'competencies' => $competencies,
'alignments' => $alignments,
'relatedbadges' => $relatedbadges,
);
@@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains competency class for displaying a badge competency.
* Contains alignment class for displaying a badge alignment.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
@@ -29,13 +29,13 @@ defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying a badge competency.
* Class for displaying a badge alignment.
*
* @package core_badges
* @copyright 2018 Dani Palou <dani@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class competency_exporter extends exporter {
class alignment_exporter extends exporter {
/**
* Return the list of properties.
@@ -46,7 +46,7 @@ class competency_exporter extends exporter {
return [
'id' => [
'type' => PARAM_INT,
'description' => 'Competency id',
'description' => 'Alignment id',
],
'badgeid' => [
'type' => PARAM_INT,
+11 -11
View File
@@ -30,7 +30,7 @@ use core\external\exporter;
use renderer_base;
use moodle_url;
use core_badges\external\endorsement_exporter;
use core_badges\external\competency_exporter;
use core_badges\external\alignment_exporter;
use core_badges\external\related_info_exporter;
/**
@@ -234,7 +234,7 @@ class user_badge_exporter extends exporter {
return array(
'context' => 'context',
'endorsement' => 'stdClass?',
'competencies' => 'stdClass[]',
'alignments' => 'stdClass[]',
'relatedbadges' => 'stdClass[]',
);
}
@@ -255,9 +255,9 @@ class user_badge_exporter extends exporter {
'description' => 'Badge endorsement',
'optional' => true,
],
'competencies' => [
'type' => competency_exporter::read_properties_definition(),
'description' => 'Badge competencies (alignment)',
'alignments' => [
'type' => alignment_exporter::read_properties_definition(),
'description' => 'Badge alignments',
'multiple' => true,
],
'relatedbadges' => [
@@ -277,13 +277,13 @@ class user_badge_exporter extends exporter {
protected function get_other_values(renderer_base $output) {
$context = $this->related['context'];
$endorsement = $this->related['endorsement'];
$competencies = $this->related['competencies'];
$alignments = $this->related['alignments'];
$relatedbadges = $this->related['relatedbadges'];
$values = array(
'badgeurl' => moodle_url::make_webservice_pluginfile_url($context->id, 'badges', 'badgeimage', $this->data->id, '/',
'f1')->out(false),
'competencies' => array(),
'alignments' => array(),
'relatedbadges' => array(),
);
@@ -292,10 +292,10 @@ class user_badge_exporter extends exporter {
$values['endorsement'] = $endorsementexporter->export($output);
}
if (!empty($competencies)) {
foreach ($competencies as $competency) {
$competencyexporter = new competency_exporter($competency, array('context' => $context));
$values['competencies'][] = $competencyexporter->export($output);
if (!empty($alignments)) {
foreach ($alignments as $alignment) {
$alignmentexporter = new alignment_exporter($alignment, array('context' => $context));
$values['alignments'][] = $alignmentexporter->export($output);
}
}
+41 -1
View File
@@ -69,6 +69,47 @@ 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'];
$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.
*
@@ -80,7 +121,6 @@ class core_badges_observer {
if (!empty($CFG->enablebadges)) {
require_once($CFG->dirroot.'/lib/badgeslib.php');
$eventdata = $event->get_record_snapshot('course_completions', $event->objectid);
$userid = $event->relateduserid;
$courseid = $event->courseid;
+19 -16
View File
@@ -81,20 +81,10 @@ define('BADGE_CRITERIA_TYPE_BADGE', 7);
define('BADGE_CRITERIA_TYPE_COHORT', 8);
/*
* Criteria type constant to class name mapping
* Competency criteria type
* Criteria type constant, primarily for storing criteria type in the database.
*/
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',
);
define('BADGE_CRITERIA_TYPE_COMPETENCY', 9);
/**
* Award criteria abstract definition
@@ -161,13 +151,17 @@ abstract class award_criteria {
* @return award_criteria
*/
public static function build($params) {
global $CFG, $BADGE_CRITERIA_TYPES;
global $CFG;
if (!isset($params['criteriatype']) || !isset($BADGE_CRITERIA_TYPES[$params['criteriatype']])) {
require_once($CFG->libdir . '/badgeslib.php');
$types = badges_list_criteria(false);
if (!isset($params['criteriatype']) || !isset($types[$params['criteriatype']])) {
print_error('error:invalidcriteriatype', 'badges');
}
$class = 'award_criteria_' . $BADGE_CRITERIA_TYPES[$params['criteriatype']];
$class = 'award_criteria_' . $types[$params['criteriatype']];
require_once($CFG->dirroot . '/badges/criteria/' . $class . '.php');
return new $class($params);
@@ -494,4 +488,13 @@ abstract class award_criteria {
}
}
}
/**
* Allow some specific criteria types to be disabled based on config.
*
* @return boolean
*/
public static function is_enabled() {
return true;
}
}
@@ -0,0 +1,283 @@
<?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 The criteria type */
public $criteriatype = BADGE_CRITERIA_TYPE_COMPETENCY;
/* @var string a required param */
public $required_param = 'competency';
/* @var array no optional params */
public $optional_params = [];
/**
* Get criteria details for displaying to users
* @param string $short Print short version of criteria
* @return string
*/
public function get_details($short = '') {
$output = array();
foreach ($this->params as $p) {
$competency = new \core_competency\competency($p['competency']);
if ($short) {
$competency->set('description', '');
}
// Render the competency even if competencies are not currently enabled.
\core_competency\api::skip_enabled();
if ($pluginsfunction = get_plugins_with_function('render_competency_summary')) {
foreach ($pluginsfunction as $plugintype => $plugins) {
foreach ($plugins as $pluginfunction) {
$output[] = $pluginfunction($competency, $competency->get_framework(), false, false, true);
}
}
}
\core_competency\api::check_enabled();
}
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
* @return array First item is a boolean to indicate an error and the second is the error message.
*/
public function get_options(&$mform) {
global $DB;
$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));
}
$badge = $DB->get_record('badge', array('id' => $this->badgeid));
$context = null;
$courseid = 0;
if ($badge->type == BADGE_TYPE_SITE) {
$context = context_system::instance();
$courseid = SITEID;
} else if ($badge->type == BADGE_TYPE_COURSE) {
$context = context_course::instance($badge->courseid);
$courseid = $badge->courseid;
}
if ($pluginsfunction = get_plugins_with_function('competency_picker')) {
foreach ($pluginsfunction as $plugintype => $plugins) {
foreach ($plugins as $pluginfunction) {
$output[] = $pluginfunction($mform, $courseid, $context, 'competency_competencies');
}
}
}
$mform->getElement('competency_competencies')->setValue($competencies);
$mform->addRule('competency_competencies', get_string('requiredcompetency', 'badges'), 'required');
// 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 = $params['competency_competencies'];
unset($params['competency_competencies']);
if (is_string($competencies)) {
$competencies = explode(',', $competencies);
}
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 = [];
if (!self::is_enabled()) {
return false;
}
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) {
$proficiency = false;
foreach ($existing as $usercompetency) {
if ($usercompetency->get('competencyid') == $param['competency']) {
$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 (!self::is_enabled()) {
return array($join, $where, $params);
}
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;
} 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);
}
/**
* Hide this criteria when competencies are disabled.
*
* @return boolean
*/
public static function is_enabled() {
return \core_competency\api::is_enabled();
}
/**
* Check if any badge has records for competencies.
*
* @param array $competencyids Array of competencies ids.
* @return boolean Return true if competencies were found in any badge.
*/
public static function has_records_for_competencies($competencyids) {
global $DB;
list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED);
$sql = "SELECT DISTINCT bc.badgeid
FROM {badge_criteria} bc
JOIN {badge_criteria_param} bcp ON bc.id = bcp.critid
WHERE bc.criteriatype = :criteriatype AND value $insql";
$params['criteriatype'] = BADGE_CRITERIA_TYPE_COMPETENCY;
return self::record_exists_sql($sql, $params);
}
}
+4 -1
View File
@@ -78,7 +78,10 @@ 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+)$/';
+32 -32
View File
@@ -222,7 +222,7 @@ class core_badges_renderer extends plugin_renderer_base {
$display .= self::print_badge_endorsement($badge);
$display .= self::print_badge_related($badge);
$display .= self::print_badge_competencies($badge);
$display .= self::print_badge_alignments($badge);
return html_writer::div($display, null, array('id' => 'badge-overview'));
}
@@ -431,12 +431,12 @@ class core_badges_renderer extends plugin_renderer_base {
$output .= html_writer::alist($items, array(), 'ul');
}
$competencies = $badge->get_alignment();
if (!empty($competencies)) {
$alignments = $badge->get_alignments();
if (!empty($alignments)) {
$output .= $this->heading(get_string('alignment', 'badges'), 3);
$items = array();
foreach ($competencies as $competency) {
$items[] = html_writer::link($competency->targeturl, $competency->targetname, array('target' => '_blank'));
foreach ($alignments as $alignment) {
$items[] = html_writer::link($alignment->targeturl, $alignment->targetname, array('target' => '_blank'));
}
$output .= html_writer::alist($items, array(), 'ul');
}
@@ -748,11 +748,11 @@ class core_badges_renderer extends plugin_renderer_base {
}
if (has_capability('moodle/badges:configuredetails', $context)) {
$competencies = $DB->count_records_sql("SELECT COUNT(bc.id)
FROM {badge_competencies} bc WHERE bc.badgeid = :badgeid", array('badgeid' => $badgeid));
$alignments = $DB->count_records_sql("SELECT COUNT(bc.id)
FROM {badge_alignment} bc WHERE bc.badgeid = :badgeid", array('badgeid' => $badgeid));
$row[] = new tabobject('balignment',
new moodle_url('/badges/competency.php', array('id' => $badgeid)),
get_string('balignment', 'badges', $competencies)
new moodle_url('/badges/alignment.php', array('id' => $badgeid)),
get_string('balignment', 'badges', $alignments)
);
}
@@ -1101,22 +1101,22 @@ class core_badges_renderer extends plugin_renderer_base {
}
/**
* Print list badge competencies.
* Print list badge alignments.
*
* @param badge $badge Badge objects.
* @return string $output List competencies to output.
* @return string $output List alignments to output.
*/
protected function print_badge_competencies(badge $badge) {
protected function print_badge_alignments(badge $badge) {
$output = '';
$output .= $this->heading(get_string('alignment', 'badges'), 3);
$competencies = $badge->get_alignment();
if (!empty($competencies)) {
$alignments = $badge->get_alignments();
if (!empty($alignments)) {
$items = array();
foreach ($competencies as $competency) {
$urlaligment = new moodle_url('competency.php',
array('id' => $badge->id, 'alignmentid' => $competency->id)
foreach ($alignments as $alignment) {
$urlaligment = new moodle_url('alignment.php',
array('id' => $badge->id, 'alignmentid' => $alignment->id)
);
$items[] = html_writer::link($urlaligment, $competency->targetname, array('target' => '_blank'));
$items[] = html_writer::link($urlaligment, $alignment->targetname, array('target' => '_blank'));
}
$output .= html_writer::alist($items, array(), 'ul');
} else {
@@ -1186,21 +1186,21 @@ class core_badges_renderer extends plugin_renderer_base {
}
/**
* Renders a table with competencies alignment.
* Renders a table with alignment.
*
* @param badge_competencies_alignment $alignment List competencies alignment.
* @return string List competencies aligment to output.
* @param badge_alignments $alignments List alignments.
* @return string List alignment to output.
*/
protected function render_badge_competencies_alignment(badge_competencies_alignment $alignment) {
$currentbadge = new badge($alignment->currentbadgeid);
$paging = new paging_bar($alignment->totalcount, $alignment->page, $alignment->perpage, $this->page->url, 'page');
protected function render_badge_alignments(badge_alignments $alignments) {
$currentbadge = new badge($alignments->currentbadgeid);
$paging = new paging_bar($alignments->totalcount, $alignments->page, $alignments->perpage, $this->page->url, 'page');
$htmlpagingbar = $this->render($paging);
$table = new html_table();
$table->attributes['class'] = 'generaltable boxaligncenter boxwidthwide';
$table->head = array('Name', 'URL', '');
foreach ($alignment->alignments as $item) {
$urlaligment = new moodle_url('competency.php',
foreach ($alignments->alignments as $item) {
$urlaligment = new moodle_url('alignment.php',
array(
'id' => $currentbadge->id,
'alignmentid' => $item->id,
@@ -1212,7 +1212,7 @@ class core_badges_renderer extends plugin_renderer_base {
);
if (!$currentbadge->is_active() && !$currentbadge->is_locked()) {
$delete = $this->output->action_icon(
new moodle_url('competency_action.php',
new moodle_url('alignment_action.php',
array(
'id' => $currentbadge->id,
'alignmentid' => $item->id,
@@ -1220,7 +1220,7 @@ class core_badges_renderer extends plugin_renderer_base {
)
), new pix_icon('t/delete', get_string('delete')));
$edit = $this->output->action_icon(
new moodle_url('competency.php',
new moodle_url('alignment.php',
array(
'id' => $currentbadge->id,
'alignmentid' => $item->id,
@@ -1480,13 +1480,13 @@ class badge_related implements renderable {
}
/**
* Collection of all competencies.
* Collection of all alignments.
*
* @copyright 2018 Tung Thai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Tung Thai <[email protected]>
*/
class badge_competencies_alignment implements renderable
class badge_alignments implements renderable
{
/** @var string how are the data sorted. */
public $sort = 'name';
@@ -1510,9 +1510,9 @@ class badge_competencies_alignment implements renderable
public $currentbadgeid = 0;
/**
* Initializes the list of competencies to display.
* Initializes the list of alignments to display.
*
* @param array $alignments List competencies alignment to render.
* @param array $alignments List alignments to render.
* @param int $currentbadgeid ID current badge.
*/
public function __construct($alignments, $currentbadgeid) {
+39 -39
View File
@@ -119,15 +119,15 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
$relatebadge->relatedbadgeid = $clonedid;
$relatebadge->relatedid = $DB->insert_record('badge_related', $relatebadge, true);
// Insert a competency aligment.
$competency = new stdClass();
$competency->badgeid = $this->coursebadge;
$competency->targetname = 'CCSS.ELA-Literacy.RST.11-12.3';
$competency->targeturl = 'http://www.corestandards.org/ELA-Literacy/RST/11-12/3';
$competency->targetdescription = 'Test target description';
$competency->targetframework = 'CCSS.RST.11-12.3';
$competency->targetcode = 'CCSS.RST.11-12.3';
$DB->insert_record('badge_competencies', $competency, true);
// Insert a aligment.
$alignment = new stdClass();
$alignment->badgeid = $this->coursebadge;
$alignment->targetname = 'CCSS.ELA-Literacy.RST.11-12.3';
$alignment->targeturl = 'http://www.corestandards.org/ELA-Literacy/RST/11-12/3';
$alignment->targetdescription = 'Test target description';
$alignment->targetframework = 'CCSS.RST.11-12.3';
$alignment->targetcode = 'CCSS.RST.11-12.3';
$DB->insert_record('badge_alignment', $alignment, true);
$this->assertion = new stdClass();
$this->assertion->badge = '{"uid":"%s","recipient":{"identity":"%s","type":"email","hashed":true,"salt":"%s"},"badge":"%s","verify":{"type":"hosted","url":"%s"},"issuedOn":"%d","evidence":"%s"}';
@@ -797,41 +797,41 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
}
/**
* Test insert, update, delete competencies alignment with a site badge.
* Test insert, update, delete alignment with a site badge.
*/
public function test_competencies_alignment() {
public function test_alignments() {
$badge = new badge($this->badgeid);
// Insert a competency alignment.
$competency1 = new stdClass();
$competency1->badgeid = $this->badgeid;
$competency1->targetname = 'CCSS.ELA-Literacy.RST.11-12.3';
$competency1->targeturl = 'http://www.corestandards.org/ELA-Literacy/RST/11-12/3';
$competency1->targetdescription = 'Test target description';
$competency1->targetframework = 'CCSS.RST.11-12.3';
$competency1->targetcode = 'CCSS.RST.11-12.3';
$competency2 = clone $competency1;
$newid1 = $badge->save_alignment($competency1);
$newid2 = $badge->save_alignment($competency2);
$competencies1 = $badge->get_alignment();
$this->assertCount(2, $competencies1);
// Insert a alignment.
$alignment1 = new stdClass();
$alignment1->badgeid = $this->badgeid;
$alignment1->targetname = 'CCSS.ELA-Literacy.RST.11-12.3';
$alignment1->targeturl = 'http://www.corestandards.org/ELA-Literacy/RST/11-12/3';
$alignment1->targetdescription = 'Test target description';
$alignment1->targetframework = 'CCSS.RST.11-12.3';
$alignment1->targetcode = 'CCSS.RST.11-12.3';
$alignment2 = clone $alignment1;
$newid1 = $badge->save_alignment($alignment1);
$newid2 = $badge->save_alignment($alignment2);
$alignments1 = $badge->get_alignments();
$this->assertCount(2, $alignments1);
$this->assertEquals($competency1->badgeid, $competencies1[$newid1]->badgeid);
$this->assertEquals($competency1->targetname, $competencies1[$newid1]->targetname);
$this->assertEquals($competency1->targeturl, $competencies1[$newid1]->targeturl);
$this->assertEquals($competency1->targetdescription, $competencies1[$newid1]->targetdescription);
$this->assertEquals($competency1->targetframework, $competencies1[$newid1]->targetframework);
$this->assertEquals($competency1->targetcode, $competencies1[$newid1]->targetcode);
$this->assertEquals($alignment1->badgeid, $alignments1[$newid1]->badgeid);
$this->assertEquals($alignment1->targetname, $alignments1[$newid1]->targetname);
$this->assertEquals($alignment1->targeturl, $alignments1[$newid1]->targeturl);
$this->assertEquals($alignment1->targetdescription, $alignments1[$newid1]->targetdescription);
$this->assertEquals($alignment1->targetframework, $alignments1[$newid1]->targetframework);
$this->assertEquals($alignment1->targetcode, $alignments1[$newid1]->targetcode);
// Update competency aligment.
$competencies1[$newid1]->targetname = 'CCSS.ELA-Literacy.RST.11-12.3 update';
$badge->save_alignment($competencies1[$newid1], $competencies1[$newid1]->id);
$competencies2 = $badge->get_alignment();
$this->assertEquals($competencies1[$newid1]->id, $competencies2[$newid1]->id);
$this->assertEquals($competencies1[$newid1]->targetname, $competencies2[$newid1]->targetname);
// Update aligment.
$alignments1[$newid1]->targetname = 'CCSS.ELA-Literacy.RST.11-12.3 update';
$badge->save_alignment($alignments1[$newid1], $alignments1[$newid1]->id);
$alignments2 = $badge->get_alignments();
$this->assertEquals($alignments1[$newid1]->id, $alignments2[$newid1]->id);
$this->assertEquals($alignments1[$newid1]->targetname, $alignments2[$newid1]->targetname);
// Delete competency alignment.
$badge->delete_alignment($competencies1[$newid2]->id);
$this->assertCount(1, $badge->get_alignment());
// Delete alignment.
$badge->delete_alignment($alignments1[$newid2]->id);
$this->assertCount(1, $badge->get_alignments());
}
}
+10 -13
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 "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"
@@ -118,7 +118,7 @@ Feature: Add badges to the system
Then I should see "Changes saved"
@javascript @_file_upload
Scenario: Competencies alignment 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,18 +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 "Competencies (0)"
And I should see "This badge does not have any competencies specified."
And I press "Add competency"
And I follow "Competency"
And I wait until the page is ready
And I follow "Competency"
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:
| Competency name | Test Badge Competencies |
| URL | https://competencies.example.com |
| Description | Test Badge Competencies description |
| Name | Test Badge Alignments |
| URL | https://alignments.example.com |
| Description | Test Badge Alignments description |
When I press "Save changes"
And I should see "Competencies (1)"
And I should see "Alignments (1)"
@javascript @_file_upload
Scenario: Add a badge from Site badges section
@@ -172,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"
@@ -0,0 +1,115 @@
@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 wait until the page is ready
And I click on "Edit" "link" in the "[data-region='configurecoursecompetencies']" "css_element"
And I wait until the page is ready
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"
When I open the autocomplete suggestions list
And I click on "ul[class='form-autocomplete-suggestions'] li" "css_element"
And I wait until the page is ready
And I press "Save"
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 until the page is ready
And I press "Save"
# Enable the badge
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"
+28 -28
View File
@@ -110,22 +110,22 @@ class core_badges_external_testcase extends externallib_advanced_testcase {
$endorsement->dateissued = $now;
$badge->save_endorsement($endorsement);
// Add 2 competencies.
$competency = new stdClass();
$competency->badgeid = $badgeid;
$competency->targetname = 'Competency 1';
$competency->targeturl = 'http://c1-target-url.domain.co.nz';
$competency->targetdescription = 'C1 target description';
$competency->targetframework = 'C1 framework';
$competency->targetcode = 'C1 code';
$badge->save_alignment($competency);
// Add 2 alignments.
$alignment = new stdClass();
$alignment->badgeid = $badgeid;
$alignment->targetname = 'Alignment 1';
$alignment->targeturl = 'http://a1-target-url.domain.co.nz';
$alignment->targetdescription = 'A1 target description';
$alignment->targetframework = 'A1 framework';
$alignment->targetcode = 'A1 code';
$badge->save_alignment($alignment);
$competency->targetname = 'Competency 2';
$competency->targeturl = 'http://c2-target-url.domain.co.nz';
$competency->targetdescription = 'C2 target description';
$competency->targetframework = 'C2 framework';
$competency->targetcode = 'C2 code';
$badge->save_alignment($competency);
$alignment->targetname = 'Alignment 2';
$alignment->targeturl = 'http://a2-target-url.domain.co.nz';
$alignment->targetdescription = 'A2 target description';
$alignment->targetframework = 'A2 framework';
$alignment->targetcode = 'A2 code';
$badge->save_alignment($alignment);
// Now a course badge.
$badge->id = null;
@@ -162,26 +162,26 @@ class core_badges_external_testcase extends externallib_advanced_testcase {
$badge->badgeurl = moodle_url::make_webservice_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/',
'f1')->out(false);
// Get the endorsement, competencies and related badges.
// Get the endorsement, alignments and related badges.
$badgeinstance = new badge($badge->id);
$endorsement = $badgeinstance->get_endorsement();
$competencies = $badgeinstance->get_alignment();
$alignments = $badgeinstance->get_alignments();
$relatedbadges = $badgeinstance->get_related_badges();
$badge->competencies = array();
$badge->alignments = array();
$badge->relatedbadges = array();
if ($endorsement) {
$badge->endorsement = (array) $endorsement;
}
if (!empty($competencies)) {
foreach ($competencies as $competency) {
// Students cannot see some fields of the competencies.
unset($competency->targetdescription);
unset($competency->targetframework);
unset($competency->targetcode);
if (!empty($alignments)) {
foreach ($alignments as $alignment) {
// Students cannot see some fields of the alignments.
unset($alignment->targetdescription);
unset($alignment->targetframework);
unset($alignment->targetcode);
$badge->competencies[] = (array) $competency;
$badge->alignments[] = (array) $alignment;
}
}
@@ -231,9 +231,9 @@ class core_badges_external_testcase extends externallib_advanced_testcase {
if (isset($badge['type']) and $badge['type'] == BADGE_TYPE_COURSE) {
$this->assertTrue(isset($badge['message']));
// Check that we have permissions to see all the data in competencies and related badges.
foreach ($badge['competencies'] as $competency) {
$this->assertTrue(isset($competency['targetdescription']));
// Check that we have permissions to see all the data in alignments and related badges.
foreach ($badge['alignments'] as $alignment) {
$this->assertTrue(isset($alignment['targetdescription']));
}
foreach ($badge['relatedbadges'] as $relatedbadge) {
+2
View File
@@ -1,5 +1,7 @@
This files describes API changes in /badges/*,
information provided here is intended especially for developers.
=== 3.7 ===
* Incorrect term "badge competencies" has been refactored to "alignments" everywhere.
=== 3.6 ===
+27 -1
View File
@@ -46,6 +46,9 @@ use required_capability_exception;
*/
class api {
/** @var boolean Allow api functions even if competencies are not enabled for the site. */
private static $skipenabled = false;
/**
* Returns whether competencies are enabled.
*
@@ -56,7 +59,30 @@ class api {
* @return boolean True when enabled.
*/
public static function is_enabled() {
return get_config('core_competency', 'enabled');
return self::$skipenabled || get_config('core_competency', 'enabled');
}
/**
* When competencies used to be enabled, we can show the text but do not include links.
*
* @return boolean True means show links.
*/
public static function show_links() {
return isloggedin() && !isguestuser() && get_config('core_competency', 'enabled');
}
/**
* Allow calls to competency api functions even if competencies are not currently enabled.
*/
public static function skip_enabled() {
self::$skipenabled = true;
}
/**
* Restore the checking that competencies are enabled with any api function.
*/
public static function check_enabled() {
self::$skipenabled = false;
}
/**
+9
View File
@@ -769,6 +769,8 @@ class competency extends persistent {
* @return bool True if we can delete the competencies.
*/
public static function can_all_be_deleted($ids) {
global $CFG;
if (empty($ids)) {
return true;
}
@@ -792,6 +794,13 @@ class competency extends persistent {
if (user_competency_plan::has_records_for_competencies($ids)) {
return false;
}
require_once($CFG->libdir . '/badgeslib.php');
// Check if competency is used in a badge.
if (badge_award_criteria_competency_has_records_for_competencies($ids)) {
return false;
}
return true;
}
@@ -53,9 +53,15 @@ class competency_framework_exporter extends \core\external\persistent_exporter {
protected function get_other_values(renderer_base $output) {
$filters = array('competencyframeworkid' => $this->persistent->get('id'));
$context = $this->persistent->get_context();
$competenciescount = 0;
try {
api::count_competencies($filters);
} catch (\required_capability_exception $re) {
$competenciescount = 0;
}
return array(
'canmanage' => has_capability('moodle/competency:competencymanage', $context),
'competenciescount' => api::count_competencies($filters),
'competenciescount' => $competenciescount,
'contextname' => $context->get_context_name(),
'contextnamenoprefix' => $context->get_context_name(false)
);
+5
View File
@@ -1,6 +1,11 @@
This files describes API changes in /competency/*. The information provided
here is intended especially for developers.
=== 3.7 ===
* tool_lp can render the ui for a competency summary or a competency picker.
Use get_plugins_with_function('competency_picker') or
get_plugins_with_function('render_competency_summary') to call it.
=== 3.3 ===
* Deprecated classes and their new equivalent:
+23 -11
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.';
@@ -456,6 +467,7 @@ $string['relative'] = 'Relative date';
$string['relatedbages'] = 'Related badges';
$string['revoke'] = 'Revoke badge';
$string['requiredcohort'] = 'At least one cohort should be added to the cohort criterion.';
$string['requiredcompetency'] = 'At least one competency should be added to the competency criterion.';
$string['requiredcourse'] = 'At least one course should be added to the courseset criterion.';
$string['requiredbadge'] = 'At least one badge should be added to the badge criterion.';
$string['reviewbadge'] = 'Changes in badge access';
@@ -495,16 +507,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:
+96 -17
View File
@@ -218,6 +218,7 @@ class badge {
* @return array
*/
public function get_accepted_criteria() {
global $CFG;
$criteriatypes = array();
if ($this->type == BADGE_TYPE_COURSE) {
@@ -226,7 +227,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,8 +238,15 @@ class badge {
BADGE_CRITERIA_TYPE_BADGE,
BADGE_CRITERIA_TYPE_PROFILE,
BADGE_CRITERIA_TYPE_COHORT,
BADGE_CRITERIA_TYPE_COMPETENCY
);
}
$alltypes = badges_list_criteria();
foreach ($criteriatypes as $index => $type) {
if (!isset($alltypes[$type])) {
unset($criteriatypes[$index]);
}
}
return $criteriatypes;
}
@@ -508,6 +517,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
@@ -714,7 +724,7 @@ class badge {
$badgecontext = $this->get_context();
$fs->delete_area_files($badgecontext->id, 'badges', 'badgeimage', $this->id);
// Delete endorsements, competencies and related badges.
// Delete endorsements, alignments and related badges.
$DB->delete_records('badge_endorsement', array('badgeid' => $this->id));
$relatedsql = 'badgeid = :badgeid OR relatedbadgeid = :relatedbadgeid';
$relatedparams = array(
@@ -722,7 +732,7 @@ class badge {
'relatedbadgeid' => $this->id
);
$DB->delete_records_select('badge_related', $relatedsql, $relatedparams);
$DB->delete_records('badge_competencies', array('badgeid' => $this->id));
$DB->delete_records('badge_alignment', array('badgeid' => $this->id));
// Finally, remove badge itself.
$DB->delete_records('badge', array('id' => $this->id));
@@ -804,43 +814,55 @@ class badge {
}
/**
* Insert/update competency alignment information of badge.
* Insert/update alignment information of badge.
*
* @param stdClass $alignment Data of a competency alignment.
* @param int $alignmentid ID competency alignment.
* @param stdClass $alignment Data of a alignment.
* @param int $alignmentid ID alignment.
* @return bool|int A status/ID when insert or update data.
*/
public function save_alignment($alignment, $alignmentid = 0) {
global $DB;
$record = $DB->record_exists('badge_competencies', array('id' => $alignmentid));
$record = $DB->record_exists('badge_alignment', array('id' => $alignmentid));
if ($record) {
$alignment->id = $alignmentid;
return $DB->update_record('badge_competencies', $alignment);
return $DB->update_record('badge_alignment', $alignment);
} else {
return $DB->insert_record('badge_competencies', $alignment, true);
return $DB->insert_record('badge_alignment', $alignment, true);
}
}
/**
* Delete a competency alignment of badge.
* Delete a alignment of badge.
*
* @param int $alignmentid ID competency alignment.
* @return bool A status for delete a competency alignment.
* @param int $alignmentid ID alignment.
* @return bool A status for delete a alignment.
*/
public function delete_alignment($alignmentid) {
global $DB;
return $DB->delete_records('badge_competencies', array('badgeid' => $this->id, 'id' => $alignmentid));
return $DB->delete_records('badge_alignment', array('badgeid' => $this->id, 'id' => $alignmentid));
}
/**
* Get competencies of badge.
* Get alignments of badge.
*
* @return array List content competencies.
* @return array List content alignments.
*/
public function get_alignments() {
global $DB;
return $DB->get_records('badge_alignment', array('badgeid' => $this->id));
}
/**
* Get alignments of badge.
*
* @deprecated since Moodle 3.7 see MDL-63876
* @return array List content alignments.
*/
public function get_alignment() {
global $DB;
return $DB->get_records('badge_competencies', array('badgeid' => $this->id));
debugging('Use of get_alignment is deprecated. Call get_alignments instead.', DEBUG_DEVELOPER);
return $this->get_alignments();
}
/**
@@ -1532,3 +1554,60 @@ function badges_setup_backpack_js() {
$PAGE->requires->js('/badges/backpack.js', true);
}
}
/**
* Return all the enabled criteria types for this site.
*
* @param boolean $enabled
* @return array
*/
function badges_list_criteria($enabled = true) {
global $CFG;
$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_COMPETENCY => 'competency',
);
if ($enabled) {
foreach ($types as $key => $type) {
$class = 'award_criteria_' . $type;
$file = $CFG->dirroot . '/badges/criteria/' . $class . '.php';
if (file_exists($file)) {
require_once($file);
if (!$class::is_enabled()) {
unset($types[$key]);
}
}
}
}
return $types;
}
/**
* Check if any badge has records for competencies.
*
* @param array $competencyids Array of competencies ids.
* @return boolean Return true if competencies were found in any badge.
*/
function badge_award_criteria_competency_has_records_for_competencies($competencyids) {
global $DB;
list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED);
$sql = "SELECT DISTINCT bc.badgeid
FROM {badge_criteria} bc
JOIN {badge_criteria_param} bcp ON bc.id = bcp.critid
WHERE bc.criteriatype = :criteriatype AND bcp.value $insql";
$params['criteriatype'] = BADGE_CRITERIA_TYPE_COMPETENCY;
return $DB->record_exists_sql($sql, $params);
}
+4 -1
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',
+2 -2
View File
@@ -3200,7 +3200,7 @@
<KEY NAME="fk_backpackid" TYPE="foreign" FIELDS="backpackid" REFTABLE="badge_backpack" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="badge_competencies" COMMENT="Defines competencies alignment for badges">
<TABLE NAME="badge_alignment" COMMENT="Defines alignment for badges">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="badgeid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
@@ -3212,7 +3212,7 @@
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="competenciesbadge" TYPE="foreign" FIELDS="badgeid" REFTABLE="badge" REFFIELDS="id"/>
<KEY NAME="alignmentsbadge" TYPE="foreign" FIELDS="badgeid" REFTABLE="badge" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="badge_related" COMMENT="Defines badge related for badges">
+24
View File
@@ -2923,5 +2923,29 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2019032200.02);
}
if ($oldversion < 2019032900.00) {
// Define table badge_competencies to be renamed to badge_alignment.
$table = new xmldb_table('badge_competencies');
// Be careful if this step gets run twice.
if ($dbman->table_exists($table)) {
$key = new xmldb_key('competenciesbadge', XMLDB_KEY_FOREIGN, ['badgeid'], 'badge', ['id']);
// Launch drop key competenciesbadge.
$dbman->drop_key($table, $key);
$key = new xmldb_key('alignmentsbadge', XMLDB_KEY_FOREIGN, ['badgeid'], 'badge', ['id']);
// Launch add key alignmentsbadge.
$dbman->add_key($table, $key);
// Launch rename table for badge_alignment.
$dbman->rename_table($table, 'badge_alignment');
}
upgrade_main_savepoint(true, 2019032900.00);
}
return true;
}
+1 -1
View File
@@ -1407,7 +1407,7 @@ function disable_output_buffering() {
*/
function is_major_upgrade_required() {
global $CFG;
$lastmajordbchanges = 2019011801.00;
$lastmajordbchanges = 2019032900.00;
$required = empty($CFG->version);
$required = $required || (float)$CFG->version < $lastmajordbchanges;
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2019032800.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2019032900.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.