diff --git a/admin/tool/lp/classes/external.php b/admin/tool/lp/classes/external.php index f798e7d50259f..d79317c1dcde5 100644 --- a/admin/tool/lp/classes/external.php +++ b/admin/tool/lp/classes/external.php @@ -205,7 +205,7 @@ public static function data_for_competencies_manage_page($competencyframeworkid, self::validate_context($framework->get_context()); $output = $PAGE->get_renderer('tool_lp'); - $renderable = new output\manage_competencies_page($framework, $params['search'], $framework->get_context()); + $renderable = new output\manage_competencies_page($framework, $params['search'], $framework->get_context(), null); $data = $renderable->export_for_template($output); diff --git a/admin/tool/lp/competencies.php b/admin/tool/lp/competencies.php index 880c89a6b9c00..facae66724f75 100644 --- a/admin/tool/lp/competencies.php +++ b/admin/tool/lp/competencies.php @@ -25,9 +25,9 @@ require_once(__DIR__ . '/../../../config.php'); require_once($CFG->libdir.'/adminlib.php'); -$id = required_param('competencyframeworkid', PARAM_INT); $pagecontextid = required_param('pagecontextid', PARAM_INT); // Reference to the context we came from. $search = optional_param('search', '', PARAM_RAW); +$id = required_param('competencyframeworkid', PARAM_INT); require_login(); \core_competency\api::require_enabled(); diff --git a/admin/tool/lp/styles.css b/admin/tool/lp/styles.css index 77339bb6a56f5..61a6cb8736f22 100644 --- a/admin/tool/lp/styles.css +++ b/admin/tool/lp/styles.css @@ -1,7 +1,8 @@ .path-admin-tool-lp [data-region="managecompetencies"] ul li, .path-admin-tool-lp [data-region="plans"] ul li, .path-admin-tool-lp [data-region="competencymovetree"] ul li, -.path-admin-tool-lp [data-region="competencylinktree"] ul li { +.path-admin-tool-lp [data-region="competencylinktree"] ul li, +.path-badges [data-region="competencylinktree"] ul li { list-style-type: none; } @@ -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%; } diff --git a/badges/amd/build/competency.min.js b/badges/amd/build/competency.min.js new file mode 100644 index 0000000000000..d57565f190aec --- /dev/null +++ b/badges/amd/build/competency.min.js @@ -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. + +/** + * Badge select competency actions + * + * @module core_badges/competency + * @package core + * @class competency + * @copyright 2019 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery', 'tool_lp/competencypicker', 'core/ajax', 'core/notification', 'core/templates'], + function($, Picker, Ajax, Notification, Templates) { + + var pickerInstance = null; + + var pageContextId = 1; + + /** + * Re-render the list of selected competencies. + * + * @method renderCompetencies + */ + var renderCompetencies = function() { + var currentCompetencies = $('[data-action="competencies"]').val(); + var requests = []; + var i = 0; + + if (currentCompetencies != '') { + currentCompetencies = currentCompetencies.split(','); + for (i = 0; i < currentCompetencies.length; i++) { + requests[requests.length] = { + methodname: 'core_competency_read_competency', + args: {id: currentCompetencies[i]} + }; + } + } + + $.when.apply($, Ajax.call(requests, false)).then(function() { + var i = 0, + competencies = []; + + for (i = 0; i < arguments.length; i++) { + competencies[i] = arguments[i]; + } + var context = { + competencies: competencies + }; + + return Templates.render('core_badges/award_criteria_competencies', context); + }).then(function(html, js) { + Templates.replaceNode($('[data-region="competencies"]'), html, js); + return true; + }).fail(Notification.exception); + + return true; + }; + + /** + * Deselect a competency + * + * @method unpickCompetenciesHandler + */ + var unpickCompetenciesHandler = function(e) { + var currentCompetencies = $('[data-action="competencies"]').val().split(','), + newCompetencies = [], + i, + toRemove = $(e.currentTarget).data('id'); + + for (i = 0; i < currentCompetencies.length; i++) { + if (currentCompetencies[i] != toRemove) { + newCompetencies[newCompetencies.length] = currentCompetencies[i]; + } + } + + $('[data-action="competencies"]').val(newCompetencies.join(',')); + + return renderCompetencies(); + }; + + /** + * Open a competencies popup to relate competencies. + * + * @method pickCompetenciesHandler + */ + var pickCompetenciesHandler = function() { + var currentCompetencies = $('[data-action="competencies"]').val().split(','); + + if (!pickerInstance) { + pickerInstance = new Picker(pageContextId, false, 'parents', true); + pickerInstance.on('save', function(e, data) { + var before = $('[data-action="competencies"]').val(); + var compIds = data.competencyIds; + if (before != '') { + compIds = compIds.concat(before.split(',')); + } + var value = compIds.join(','); + + $('[data-action="competencies"]').val(value); + + return renderCompetencies(); + }); + } + + pickerInstance.setDisallowedCompetencyIDs(currentCompetencies); + pickerInstance.display(); + }; + + return /** @alias module:core_badges/competency */ { + /** + * Listen for clicks on the competency picker and push the changes to the form element. + * + * @method init + * @param {Integer} contextId + */ + init: function(contextId) { + pageContextId = contextId; + renderCompetencies(); + $('[data-action="select-competencies"]').on('click', pickCompetenciesHandler); + $('body').on('click', '[data-action="deselect-competency"]', unpickCompetenciesHandler); + } + }; +}); diff --git a/badges/classes/observer.php b/badges/classes/observer.php index 8754ab4d6e498..f33a7f01c4df2 100644 --- a/badges/classes/observer.php +++ b/badges/classes/observer.php @@ -69,6 +69,48 @@ public static function course_module_criteria_review(\core\event\course_module_c } } + /** + * Triggered when '\core\event\competency_evidence_created' event is triggered. + * + * @param \core\event\competency_evidence_created $event + */ + public static function competency_criteria_review(\core\event\competency_evidence_created $event) { + global $DB, $CFG; + + if (!empty($CFG->enablebadges)) { + require_once($CFG->dirroot.'/lib/badgeslib.php'); + + if (!get_config('core_competency', 'enabled')) { + return; + } + + $ucid = $event->other['usercompetencyid']; + $cid = $event->other['competencyid']; + $eventdata = $event->get_record_snapshot('competency_usercomp', $ucid); + $userid = $event->relateduserid; + + if ($rs = $DB->get_records('badge_criteria_param', array('name' => 'competency_' . $cid, 'value' => $cid))) { + foreach ($rs as $r) { + $crit = $DB->get_record('badge_criteria', array('id' => $r->critid), 'badgeid, criteriatype', MUST_EXIST); + $badge = new badge($crit->badgeid); + // Only site badges are updated from site competencies. + if (!$badge->is_active() || $badge->is_issued($userid)) { + continue; + } + + if ($badge->criteria[$crit->criteriatype]->review($userid)) { + $badge->criteria[$crit->criteriatype]->mark_complete($userid); + + if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) { + $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid); + $badge->issue($userid); + } + } + } + } + } + } + /** * Triggered when 'course_completed' event is triggered. * diff --git a/badges/criteria/award_criteria.php b/badges/criteria/award_criteria.php index 0fb86e18e956a..6f689d0df4b37 100644 --- a/badges/criteria/award_criteria.php +++ b/badges/criteria/award_criteria.php @@ -80,20 +80,27 @@ */ define('BADGE_CRITERIA_TYPE_COHORT', 8); +/* + * Competency criteria type + * Criteria type constant, primarily for storing criteria type in the database. + */ +define('BADGE_CRITERIA_TYPE_COMPETENCY', 9); + /* * Criteria type constant to class name mapping */ global $BADGE_CRITERIA_TYPES; $BADGE_CRITERIA_TYPES = array( - BADGE_CRITERIA_TYPE_OVERALL => 'overall', - BADGE_CRITERIA_TYPE_ACTIVITY => 'activity', - BADGE_CRITERIA_TYPE_MANUAL => 'manual', - BADGE_CRITERIA_TYPE_SOCIAL => 'social', - BADGE_CRITERIA_TYPE_COURSE => 'course', - BADGE_CRITERIA_TYPE_COURSESET => 'courseset', - BADGE_CRITERIA_TYPE_PROFILE => 'profile', - BADGE_CRITERIA_TYPE_BADGE => 'badge', - BADGE_CRITERIA_TYPE_COHORT => 'cohort', + BADGE_CRITERIA_TYPE_OVERALL => 'overall', + BADGE_CRITERIA_TYPE_ACTIVITY => 'activity', + BADGE_CRITERIA_TYPE_MANUAL => 'manual', + BADGE_CRITERIA_TYPE_SOCIAL => 'social', + BADGE_CRITERIA_TYPE_COURSE => 'course', + BADGE_CRITERIA_TYPE_COURSESET => 'courseset', + BADGE_CRITERIA_TYPE_PROFILE => 'profile', + BADGE_CRITERIA_TYPE_BADGE => 'badge', + BADGE_CRITERIA_TYPE_COHORT => 'cohort', + BADGE_CRITERIA_TYPE_COMPETENCY => 'competency', ); /** diff --git a/badges/criteria/award_criteria_competency.php b/badges/criteria/award_criteria_competency.php new file mode 100644 index 0000000000000..9866b3b5261f0 --- /dev/null +++ b/badges/criteria/award_criteria_competency.php @@ -0,0 +1,237 @@ +. + +/** + * This file contains the badge earned badge award criteria type class + * + * @package core + * @subpackage badges + * @copyright 2019 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Badge award criteria -- award on competency completion + * + * @package core + * @subpackage badges + * @copyright 2019 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class award_criteria_competency extends award_criteria { + + /* @var int Criteria [BADGE_CRITERIA_TYPE_COMPETENCY] */ + public $criteriatype = BADGE_CRITERIA_TYPE_COMPETENCY; + + public $required_param = 'competency'; + public $self_validation = true; + public $optional_params = array(''); + + /** + * Get criteria details for displaying to users + * @param string $short Print short version of criteria + * @return string + */ + public function get_details($short = '') { + global $DB, $OUTPUT; + $output = array(); + + foreach ($this->params as $p) { + $competency = new \core_competency\competency($p['competency']); + if ($short) { + $competency->set('description', ''); + } + $summary = new \tool_lp\output\competency_summary($competency, $competency->get_framework(), !$short, !$short); + $str = $OUTPUT->render($summary); + $output[] = $str; + } + + return '
' . + implode('
', $output) . + '
'; + } + + /** + * Add appropriate new criteria options to the form + * @param object $mform moodle form + */ + public function get_options(&$mform) { + global $DB, $PAGE; + $none = false; + $availablebadges = null; + + $mform->addElement('header', 'first_header', $this->get_title()); + $mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges'); + + // Determine if this badge is a course badge or a site badge. + $competencies = ''; + if (count($this->params)) { + $competencies = implode(',', array_keys($this->params)); + } + $mform->addElement('static', 'competenciesdescription', '', '
'); + $mform->addElement('hidden', 'competency', $competencies, ['data-action' => 'competencies']); + + $mform->setType('competency', PARAM_RAW); + $badge = $DB->get_record('badge', array('id' => $this->badgeid)); + if ($badge->type == BADGE_TYPE_SITE) { + $context = context_system::instance(); + } else if ($badge->type == BADGE_TYPE_COURSE) { + $context = context_course::instance($badge->courseid); + } + $params = [$context->id]; + // Require some JS to select the competencies. + $PAGE->requires->js_call_amd('core_badges/competency', 'init', $params); + + $mform->addElement('button', 'select_competencies', get_string('addcompetency', 'badges'), ['data-action' => 'select-competencies']); + + // Add aggregation. + if (!$none) { + $mform->addElement('header', 'aggregation', get_string('method', 'badges')); + $agg = array(); + $agg[] =& $mform->createElement('radio', 'agg', '', get_string('allmethodcompetencies', 'badges'), 1); + $agg[] =& $mform->createElement('radio', 'agg', '', get_string('anymethodcompetencies', 'badges'), 2); + $mform->addGroup($agg, 'methodgr', '', array('
'), false); + if ($this->id !== 0) { + $mform->setDefault('agg', $this->method); + } else { + $mform->setDefault('agg', BADGE_CRITERIA_AGGREGATION_ANY); + } + } + + return array($none, get_string('noparamstoadd', 'badges')); + } + + /** + * Save criteria records + * + * @param array $params Values from the form or any other array. + */ + public function save($params = array()) { + $competencies = explode(',', $params['competency']); + unset($params['competency']); + foreach ($competencies as $competencyid) { + $params["competency_{$competencyid}"] = $competencyid; + } + parent::save($params); + } + + /** + * Review this criteria and decide if it has been completed + * + * @param int $userid User whose criteria completion needs to be reviewed. + * @param bool $filtered An additional parameter indicating that user list + * has been reduced and some expensive checks can be skipped. + * + * @return bool Whether criteria is complete. + */ + public function review($userid, $filtered = false) { + global $DB; + + $overall = false; + $competencyids = []; + + foreach ($this->params as $param) { + $competencyids[] = $param['competency']; + } + + $existing = []; + $badge = $DB->get_record('badge', array('id' => $this->badgeid)); + if ($badge->type == BADGE_TYPE_SITE) { + $existing = \core_competency\user_competency::get_multiple($userid, $competencyids); + } else if ($badge->type == BADGE_TYPE_COURSE) { + $existing = \core_competency\user_competency_course::get_multiple($userid, $badge->courseid, $competencyids); + } + + foreach ($this->params as $param) { + $found = false; + $proficiency = false; + foreach ($existing as $usercompetency) { + if ($usercompetency->get('competencyid') == $param['competency']) { + $found = true; + $proficiency = $usercompetency->get('proficiency'); + } + } + + if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) { + if (!$proficiency) { + return false; + } + } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) { + if ($proficiency) { + return true; + } + } + } + + return $overall; + } + + /** + * Returns array with sql code and parameters returning all ids + * of users who meet this particular criterion. + * + * @return array list($join, $where, $params) + */ + public function get_completed_criteria_sql() { + global $DB; + + $join = ''; + $where = ''; + $params = []; + $competencyids = []; + + $badge = $DB->get_record('badge', array('id' => $this->badgeid)); + + if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) { + // User has received ANY of the required competencies (we can use an in or equals list). + foreach ($this->params as $param) { + $competencyids[] = $param['competency']; + } + + $where = ' AND uc2.competencyid '; + list($sql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED, 'usercomp'); + $where .= $sql; + if ($badge->type == BADGE_TYPE_SITE) { + $join = ' JOIN {competency_usercomp} uc2 ON uc2.userid = u.id'; + } else if ($badge->type == BADGE_TYPE_COURSE) { + $join = ' JOIN {competency_usercompcourse} uc2 ON uc2.userid = u.id AND uc2.courseid = :competencycourseid '; + $params['competencycourseid'] = $badge->courseid; + } + $where .= ' AND uc2.proficiency = :isproficient '; + $params['isproficient'] = true; + return array($join, $where, $params); + } else { + + // User has received ALL of the required competencies (we have to join on each one). + $joincount = 0; + foreach ($this->params as $param) { + $joincount++; + $join .= ' JOIN {competency_usercomp} uc' . $joincount . ' ON uc' . $joincount . '.userid = u.id'; + $where .= ' AND uc' . $joincount . '.competencyid = :competencyindex' . $joincount; + $params['competencyindex' . $joincount] = $param['competency']; + + $where .= ' AND uc' . $joincount . '.userid = u.id'; + $where .= ' AND uc' . $joincount . '.proficiency = :isproficient' . $joincount; + $params['isproficient' . $joincount] = true; + } + + return array($join, $where, $params); + } + return array($join, $where, $params); + } +} diff --git a/badges/criteria_form.php b/badges/criteria_form.php index c8d18d14729fc..efd08ecff4f83 100644 --- a/badges/criteria_form.php +++ b/badges/criteria_form.php @@ -78,7 +78,7 @@ public function validation($data, $files) { $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+)$/'; diff --git a/badges/templates/award_criteria_competencies.mustache b/badges/templates/award_criteria_competencies.mustache new file mode 100644 index 0000000000000..3113597f4f19d --- /dev/null +++ b/badges/templates/award_criteria_competencies.mustache @@ -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 . +}} +{{! + @template core_badges/award_criteria_competencies + + List of competencies for a badge. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * competencies array + * id int + * shortname string + * idnumber string + + Example context (json): + { + "competencies": + [ + { + "id": 1, + "shortname": "Competency", + "idnumber": "C1" + } + ] + } + +}} +
+ +{{^competencies}} + {{#str}}nocompetencies, core_badges{{/str}} +{{/competencies}} +
+{{#js}} +require(['tool_lp/competencydialogue'], function(Compdialogue) { + Compdialogue.init(); +}); +{{/js}} diff --git a/badges/tests/behat/add_badge.feature b/badges/tests/behat/add_badge.feature index 235fdd0bfd374..c54b693655014 100644 --- a/badges/tests/behat/add_badge.feature +++ b/badges/tests/behat/add_badge.feature @@ -46,7 +46,7 @@ Feature: Add badges to the system And I should see "Test badge with 'apostrophe' and other friends (&@#)" And I should see "Endorsement" And I should see "Related badges (0)" - And I should see "External alignments (0)" + And I should see "Alignments (0)" And I should not see "Create badge" And I follow "Manage badges" And I should see "Number of badges available: 1" @@ -118,7 +118,7 @@ Feature: Add badges to the system Then I should see "Changes saved" @javascript @_file_upload - Scenario: External alignments for Badge + Scenario: Alignments for Badge Given I navigate to "Badges > Add a new badge" in site administration And I set the following fields to these values: | Name | Test Badge | @@ -133,15 +133,15 @@ Feature: Add badges to the system When I press "Create badge" Then I should see "Test Badge" And I should see "Endorsement" - And I follow "External alignments (0)" - And I should see "This badge does not have any external alignments specified." - And I press "Add external alignment" + And I follow "Alignments (0)" + And I should see "This badge does not have any external skills or standards specified." + And I press "Add external skill or standard" And I set the following fields to these values: - | External alignment name | Test Badge Alignments | + | Name | Test Badge Alignments | | URL | https://alignments.example.com | | Description | Test Badge Alignments description | When I press "Save changes" - And I should see "External alignments (1)" + And I should see "Alignments (1)" @javascript @_file_upload Scenario: Add a badge from Site badges section @@ -169,7 +169,7 @@ Feature: Add badges to the system And I should see "Test badge with 'apostrophe' and other friends (&@#) 2" And I should see "Endorsement" And I should see "Related badges (0)" - And I should see "Competencies (0)" + And I should see "Alignments (0)" And I should not see "Create badge" And I follow "Manage badges" And I should see "Number of badges available: 1" diff --git a/badges/tests/behat/criteria_competency.feature b/badges/tests/behat/criteria_competency.feature new file mode 100644 index 0000000000000..17b4eb230eca9 --- /dev/null +++ b/badges/tests/behat/criteria_competency.feature @@ -0,0 +1,119 @@ +@core @core_badges @_file_upload +Feature: Award badges based on competency completion + In order to award badges to users based on competency completion + As an admin + I need to add competency completion criteria to badges in the system + + Background: Setup the competency framework and the course + Given the following "users" exist: + | username | firstname | lastname | email | + | user1 | First | User | first@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | user1 | C1 | student | + And the following lp "frameworks" exist: + | shortname | idnumber | + | Framework 1 | sc-y-2 | + And the following lp "competencies" exist: + | shortname | framework | + | comp1 | sc-y-2 | + And I log in as "admin" + + @javascript + Scenario: Award badge for completing a competency in a course + # Add a competency to the course + When I am on "Course 1" course homepage + And I follow "Competencies" + And I press "Add competencies to course" + And "Competency picker" "dialogue" should be visible + And I select "comp1" of the competency tree + And I click on "Add" "button" in the "Competency picker" "dialogue" + And I click on "Edit" "link" in the "[data-region='configurecoursecompetencies']" "css_element" + And I click on "Rating a competency only updates the competency in this course" "text" + And I click on "Save changes" "button" in the "Configure course competencies" "dialogue" + # Add a badge to the course + And I am on "Course 1" course homepage + And I navigate to "Badges > Add a new badge" in current page administration + And I follow "Add a new badge" + And I set the following fields to these values: + | Name | Course Badge | + | Description | Course badge description | + | issuername | Tester of course badge | + And I upload "badges/tests/behat/badge.png" file to "Image" filemanager + And I press "Create badge" + # Set the competency as a criteria for the badge + And I set the field "type" to "Competencies" + And I press "Add competency" + And "Competency picker" "dialogue" should be visible + And I select "comp1" of the competency tree + And I click on "Add" "button" in the "Competency picker" "dialogue" + # And I wait "1" seconds + And I wait until the page is ready + And I press "Save" + # And I wait "1" seconds + And I wait until the page is ready + # Enable the badge + And I press "Enable access" + And I press "Continue" + # Rate the competency in the course + And I am on "Course 1" course homepage + And I follow "Competencies" + And I click on "comp1" "link" in the "[data-region='coursecompetencies']" "css_element" + And I press "Rate" + And I set the following fields to these values: + | Rating | C | + And I click on "Rate" "button" in the "Rate" "dialogue" + And I log out + # See if we got the badge + Then I log in as "user1" + And I follow "Profile" in the user menu + And I should see "Course Badge" + + @javascript + Scenario: Award badge for completing a competency in the site + # Add a competency to the course + When I am on "Course 1" course homepage + And I follow "Competencies" + And I press "Add competencies to course" + And "Competency picker" "dialogue" should be visible + And I select "comp1" of the competency tree + And I click on "Add" "button" in the "Competency picker" "dialogue" + # Add a badge to the site + And I navigate to "Badges > Add a new badge" in site administration + And I set the following fields to these values: + | Name | Site Badge | + | Description | Site badge description | + | issuername | Tester of site badge | + And I upload "badges/tests/behat/badge.png" file to "Image" filemanager + And I press "Create badge" + # Set the competency as a criteria for the badge + And I set the field "type" to "Competencies" + And I press "Add competency" + And "Competency picker" "dialogue" should be visible + And I select "comp1" of the competency tree + And I click on "Add" "button" in the "Competency picker" "dialogue" + # And I wait "1" seconds + And I wait until the page is ready + And I press "Save" + # Enable the badge + # And I wait "1" seconds + And I wait until the page is ready + And I press "Enable access" + And I press "Continue" + # Rate the competency in the course + And I am on "Course 1" course homepage + And I follow "Competencies" + And I click on "comp1" "link" in the "[data-region='coursecompetencies']" "css_element" + And I press "Rate" + And I set the following fields to these values: + | Rating | C | + And I wait until the page is ready + And I click on "Rate" "button" in the "Rate" "dialogue" + And I log out + # See if we got the badge + Then I log in as "user1" + And I follow "Profile" in the user menu + And I should see "Site Badge" diff --git a/lang/en/badges.php b/lang/en/badges.php index 435c7c8190dd8..a959a438f4e71 100644 --- a/lang/en/badges.php +++ b/lang/en/badges.php @@ -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['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['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['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_short6'] = 'Complete {$a} of: '; $string['criteria_descr_short7'] = 'Complete {$a} of: '; $string['criteria_descr_short8'] = 'Cohort membership in {$a} of: '; +$string['criteria_descr_short9'] = 'Complete {$a} 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_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_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 {$a} of the listed requirements.'; $string['criteria_descr_1'] = '{$a} of the following activities are completed:'; $string['criteria_descr_2'] = 'This badge has to be awarded by the users with {$a} of the following roles:'; @@ -219,6 +226,7 @@ $string['criteria_descr_6'] = '{$a} of the following user profile fields have to be completed:'; $string['criteria_descr_7'] = '{$a} of the following badges have to be earned:'; $string['criteria_descr_8'] = 'Membership in {$a} of the following cohorts is required:'; +$string['criteria_descr_9'] = '{$a} 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_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['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.
'; $string['nobackpackbadges'] = 'There are no badges in the collections you have selected. Add more collections.'; $string['nobackpackcollections'] = 'No badge collections have been selected. Add collections.'; $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['notacceptedrole'] = 'Your current role assignment is not among the roles that can manually issue this badge.
If you would like to see users who have already earned this badge, you can visit {$a} page. '; $string['notconnected'] = 'Not connected'; -$string['notealignment'] = 'External competencies, skills or standards which the badge covers may be specified. Any competencies are displayed on the badge page.'; +$string['notealignment'] = 'External skills or standards, which the badge is aligned with, may be specified. Any external skills or standards are displayed on the badge page.'; $string['noteendorsement'] = 'An endorsement from a third party may be used to add value to the badge. For example, a badge issued by a teacher may be endorsed by the school, or a badge issued by a local awarding body may be endorsed by the national awarding body.'; $string['noterelated'] = 'Badges with a connection may be marked as related. For example, badges with the same criteria which are displayed in different languages may be marked as related. Any related badges are displayed on the badge page.'; $string['nothingtoadd'] = 'There are no available criteria to add.'; @@ -495,16 +506,16 @@ 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: diff --git a/lib/badgeslib.php b/lib/badgeslib.php index 98c1b9e3746dd..a95d2371b39a0 100644 --- a/lib/badgeslib.php +++ b/lib/badgeslib.php @@ -226,7 +226,8 @@ public function get_accepted_criteria() { BADGE_CRITERIA_TYPE_MANUAL, BADGE_CRITERIA_TYPE_COURSE, BADGE_CRITERIA_TYPE_BADGE, - BADGE_CRITERIA_TYPE_ACTIVITY + BADGE_CRITERIA_TYPE_ACTIVITY, + BADGE_CRITERIA_TYPE_COMPETENCY ); } else if ($this->type == BADGE_TYPE_SITE) { $criteriatypes = array( @@ -236,6 +237,7 @@ public function get_accepted_criteria() { BADGE_CRITERIA_TYPE_BADGE, BADGE_CRITERIA_TYPE_PROFILE, BADGE_CRITERIA_TYPE_COHORT, + BADGE_CRITERIA_TYPE_COMPETENCY ); } @@ -508,6 +510,7 @@ public function review_all_criteria() { } list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql(); + // For site level badges, get all active site users who can earn this badge and haven't got it yet. if ($this->type == BADGE_TYPE_SITE) { $sql = "SELECT DISTINCT u.id, bi.badgeid @@ -838,7 +841,7 @@ public function delete_alignment($alignmentid) { * * @return array List content alignments. */ - public function get_alignment() { + public function get_alignments() { global $DB; return $DB->get_records('badge_alignment', array('badgeid' => $this->id)); } diff --git a/lib/db/events.php b/lib/db/events.php index 0e1e3e17b1c96..2be83ee60c6ee 100644 --- a/lib/db/events.php +++ b/lib/db/events.php @@ -62,7 +62,10 @@ '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',