diff --git a/badges/assertion.php b/badges/assertion.php index 24b8e603310..0bd944e0ea9 100644 --- a/badges/assertion.php +++ b/badges/assertion.php @@ -25,16 +25,24 @@ */ require_once(dirname(dirname(__FILE__)) . '/config.php'); -require_once($CFG->libdir . '/badgeslib.php'); if (empty($CFG->enablebadges)) { print_error('badgesdisabled', 'badges'); } -$hash = required_param('b', PARAM_ALPHANUM); +$hash = required_param('b', PARAM_ALPHANUM); // Issued badge unique hash for badge assertion. +$action = optional_param('action', null, PARAM_BOOL); // Generates badge class if true. -$badge = badges_get_issued_badge_info($hash); +$assertion = new core_badges_assertion($hash); + +if (!is_null($action)) { + // Get badge class or issuer information depending on $action. + $json = ($action) ? $assertion->get_badge_class() : $assertion->get_issuer(); +} else { + // Otherwise, get badge assertion. + $json = $assertion->get_badge_assertion(); +} header('Content-type: application/json; charset=utf-8'); -echo json_encode($badge); \ No newline at end of file +echo json_encode($json); diff --git a/badges/badge.php b/badges/badge.php index 339b147313e..c2f7a63300a 100644 --- a/badges/badge.php +++ b/badges/badge.php @@ -35,8 +35,8 @@ $output = $PAGE->get_renderer('core', 'badges'); $badge = new issued_badge($id); -if ($bake && ($badge->recipient == $USER->id)) { - $name = str_replace(' ', '_', $badge->issued['badge']['name']) . '.png'; +if ($bake && ($badge->recipient->id == $USER->id)) { + $name = str_replace(' ', '_', $badge->badgeclass['name']) . '.png'; ob_start(); $file = badges_bake($id, $badge->badgeid); header('Content-Type: image/png'); @@ -50,8 +50,8 @@ $PAGE->set_pagelayout('base'); $PAGE->set_title(get_string('issuedbadge', 'badges')); if (isloggedin()) { - $PAGE->set_heading($badge->issued['badge']['name']); - $PAGE->navbar->add($badge->issued['badge']['name']); + $PAGE->set_heading($badge->badgeclass['name']); + $PAGE->navbar->add($badge->badgeclass['name']); $url = new moodle_url('/badges/mybadges.php'); navigation_node::override_active_url($url); } diff --git a/badges/classes/assertion.php b/badges/classes/assertion.php new file mode 100644 index 00000000000..ba0efb5a2f4 --- /dev/null +++ b/badges/classes/assertion.php @@ -0,0 +1,158 @@ +. + +/** + * Badge assertion library. + * + * @package core + * @subpackage badges + * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Yuliya Bozhko + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Open Badges Assertions specification 1.0 {@link https://github.com/mozilla/openbadges/wiki/Assertions} + * + * Badge asserion is defined by three parts: + * - Badge Assertion (information regarding a specific badge that was awarded to a badge earner) + * - Badge Class (general information about a badge and what it is intended to represent) + * - Issuer Class (general information of an issuing organisation) + */ + +/** + * Class that represents badge assertion. + * + */ +class core_badges_assertion { + /** @var object Issued badge information from database */ + private $_data; + + /** @var moodle_url Issued badge url */ + private $_url; + + /** + * Constructs with issued badge unique hash. + * + * @param string $hash Badge unique hash from badge_issued table. + */ + public function __construct($hash) { + global $DB; + + $this->_data = $DB->get_record_sql(' + SELECT + bi.dateissued, + bi.dateexpire, + bi.uniquehash, + u.email, + b.*, + bb.email as backpackemail + FROM + {badge} b + JOIN {badge_issued} bi + ON b.id = bi.badgeid + JOIN {user} u + ON u.id = bi.userid + LEFT JOIN {badge_backpack} bb + ON bb.userid = bi.userid + WHERE ' . $DB->sql_compare_text('bi.uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40), + array('hash' => $hash), IGNORE_MISSING); + + $this->_url = new moodle_url('/badges/badge.php', array('hash' => $this->_data->uniquehash)); + } + + /** + * Get badge assertion. + * + * @return array Badge assertion. + */ + public function get_badge_assertion() { + global $CFG; + $assertion = array(); + if ($this->_data) { + $hash = $this->_data->uniquehash; + $email = empty($this->_data->backpackemail) ? $this->_data->email : $this->_data->backpackemail; + $assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash)); + $classurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'action' => 1)); + + // Required. + $assertion['uid'] = $hash; + $assertion['recipient'] = array(); + $assertion['recipient']['identity'] = 'sha256$' . hash('sha256', $email . $CFG->badges_badgesalt); + $assertion['recipient']['type'] = 'email'; // Currently the only supported type. + $assertion['recipient']['hashed'] = true; // We are always hashing recipient. + $assertion['recipient']['salt'] = $CFG->badges_badgesalt; + $assertion['badge'] = $classurl->out(false); + $assertion['verify'] = array(); + $assertion['verify']['type'] = 'hosted'; // 'Signed' is not implemented yet. + $assertion['verify']['url'] = $assertionurl->out(false); + $assertion['issuedOn'] = $this->_data->dateissued; + // Optional. + $assertion['evidence'] = $this->_url->out(false); // Currently issued badge URL. + if (!empty($this->_data->dateexpire)) { + $assertion['expires'] = $this->_data->dateexpire; + } + } + return $assertion; + } + + /** + * Get badge class information. + * + * @return array Badge Class information. + */ + public function get_badge_class() { + $class = array(); + if ($this->_data) { + if (empty($this->_data->courseid)) { + $context = context_system::instance(); + } else { + $context = context_course::instance($this->_data->courseid); + } + $issuerurl = new moodle_url('/badges/assertion.php', array('b' => $this->_data->uniquehash, 'action' => 0)); + + // Required. + $class['name'] = $this->_data->name; + $class['description'] = $this->_data->description; + $class['image'] = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f1')->out(false); + $class['criteria'] = $this->_url->out(false); // Currently issued badge URL. + $class['issuer'] = $issuerurl->out(false); + } + return $class; + } + + /** + * Get badge issuer information. + * + * @return array Issuer information. + */ + public function get_issuer() { + $issuer = array(); + if ($this->_data) { + // Required. + $issuer['name'] = $this->_data->issuername; + $issuer['url'] = $this->_data->issuerurl; + // Optional. + if (!empty($this->_data->issuercontact)) { + $issuer['email'] = $this->_data->issuercontact; + } + } + return $issuer; + } + +} diff --git a/badges/lib/bakerlib.php b/badges/lib/bakerlib.php index 8a0c45e611e..9507bfa44c1 100644 --- a/badges/lib/bakerlib.php +++ b/badges/lib/bakerlib.php @@ -120,25 +120,11 @@ class PNG_MetaDataHandler debugging('Key is too big'); } - if ($type == 'iTXt') { - // iTXt International textual data. - // Keyword: 1-79 bytes (character string) - // Null separator: 1 byte - // Compression flag: 1 byte - // Compression method: 1 byte - // Language tag: 0 or more bytes (character string) - // Null separator: 1 byte - // Translated keyword: 0 or more bytes - // Null separator: 1 byte - // Text: 0 or more bytes - $data = $key . "\000'json'\0''\0\"{'method': 'hosted', 'assertionUrl': '" . $value . "'}\""; - } else { - // tEXt Textual data. - // Keyword: 1-79 bytes (character string) - // Null separator: 1 byte - // Text: n bytes (character string) - $data = $key . "\0" . $value; - } + // tEXt Textual data. + // Keyword: 1-79 bytes (character string) + // Null separator: 1 byte + // Text: n bytes (character string) + $data = $key . "\0" . $value; $crc = pack("N", crc32($type . $data)); $len = pack("N", strlen($data)); diff --git a/badges/renderer.php b/badges/renderer.php index 3075dc1f97b..f138e747f2b 100644 --- a/badges/renderer.php +++ b/badges/renderer.php @@ -278,24 +278,24 @@ class core_badges_renderer extends plugin_renderer_base { global $USER, $CFG, $DB; $issued = $ibadge->issued; $userinfo = $ibadge->recipient; + $badgeclass = $ibadge->badgeclass; $badge = new badge($ibadge->badgeid); - $today_date = date('Y-m-d'); - $today = strtotime($today_date); + $now = time(); $table = new html_table(); $table->id = 'issued-badge-table'; $imagetable = new html_table(); $imagetable->attributes = array('class' => 'clearfix badgeissuedimage'); - $imagetable->data[] = array(html_writer::empty_tag('img', array('src' => $issued['badge']['image']))); + $imagetable->data[] = array(html_writer::empty_tag('img', array('src' => $badgeclass['image']))); if ($USER->id == $userinfo->id && !empty($CFG->enablebadges)) { $imagetable->data[] = array($this->output->single_button( - new moodle_url('/badges/badge.php', array('hash' => $ibadge->hash, 'bake' => true)), + new moodle_url('/badges/badge.php', array('hash' => $issued['uid'], 'bake' => true)), get_string('download'), 'POST')); - $expiration = isset($issued['expires']) ? strtotime($issued['expires']) : $today + 1; - if (!empty($CFG->badges_allowexternalbackpack) && ($expiration > $today) && badges_user_has_backpack($USER->id)) { - $assertion = new moodle_url('/badges/assertion.php', array('b' => $ibadge->hash)); + $expiration = isset($issued['expires']) ? $issued['expires'] : $now + 86400; + if (!empty($CFG->badges_allowexternalbackpack) && ($expiration > $now) && badges_user_has_backpack($USER->id)) { + $assertion = new moodle_url('/badges/assertion.php', array('b' => $issued['uid'])); $action = new component_action('click', 'addtobackpack', array('assertion' => $assertion->out(false))); $attributes = array( 'type' => 'button', @@ -335,24 +335,23 @@ class core_badges_renderer extends plugin_renderer_base { $datatable->data[] = array(get_string('bcriteria', 'badges'), self::print_badge_criteria($badge)); $datatable->data[] = array($this->output->heading(get_string('issuancedetails', 'badges'), 3), ''); - $datatable->data[] = array(get_string('dateawarded', 'badges'), $issued['issued_on']); + $datatable->data[] = array(get_string('dateawarded', 'badges'), userdate($issued['issuedOn'])); if (isset($issued['expires'])) { - $expiration = strtotime($issued['expires']); - if ($expiration < $today) { - $cell = new html_table_cell($issued['expires'] . get_string('warnexpired', 'badges')); + if ($issued['expires'] < $now) { + $cell = new html_table_cell(userdate($issued['expires']) . get_string('warnexpired', 'badges')); $cell->attributes = array('class' => 'notifyproblem warning'); $datatable->data[] = array(get_string('expirydate', 'badges'), $cell); $image = html_writer::start_tag('div', array('class' => 'badge')); - $image .= html_writer::empty_tag('img', array('src' => $issued['badge']['image'])); + $image .= html_writer::empty_tag('img', array('src' => $badgeclass['image'])); $image .= $this->output->pix_icon('i/expired', - get_string('expireddate', 'badges', $issued['expires']), + get_string('expireddate', 'badges', userdate($issued['expires'])), 'moodle', array('class' => 'expireimage')); $image .= html_writer::end_tag('div'); $imagetable->data[0] = array($image); } else { - $datatable->data[] = array(get_string('expirydate', 'badges'), $issued['expires']); + $datatable->data[] = array(get_string('expirydate', 'badges'), userdate($issued['expires'])); } } @@ -905,15 +904,15 @@ class issued_badge implements renderable { /** @var badge recipient */ public $recipient; + /** @var badge class */ + public $badgeclass; + /** @var badge visibility to others */ public $visible = 0; /** @var badge class */ public $badgeid = 0; - /** @var issued badge unique hash */ - public $hash = ""; - /** * Initializes the badge to display * @@ -921,8 +920,10 @@ class issued_badge implements renderable { */ public function __construct($hash) { global $DB; - $this->issued = badges_get_issued_badge_info($hash); - $this->hash = $hash; + + $assertion = new core_badges_assertion($hash); + $this->issued = $assertion->get_badge_assertion(); + $this->badgeclass = $assertion->get_badge_class(); $rec = $DB->get_record_sql('SELECT userid, visible, badgeid FROM {badge_issued} diff --git a/badges/tests/badgeslib_test.php b/badges/tests/badgeslib_test.php index a3b28782d8c..a5715695899 100644 --- a/badges/tests/badgeslib_test.php +++ b/badges/tests/badgeslib_test.php @@ -35,6 +35,7 @@ class core_badgeslib_testcase extends advanced_testcase { protected $user; protected $module; protected $coursebadge; + protected $assertion; protected function setUp() { global $DB, $CFG; @@ -52,6 +53,7 @@ class core_badgeslib_testcase extends advanced_testcase { $fordb->usermodified = $user->id; $fordb->issuername = "Test issuer"; $fordb->issuerurl = "http://issuer-url.domain.co.nz"; + $fordb->issuercontact = "issuer@example.com"; $fordb->expiredate = null; $fordb->expireperiod = null; $fordb->type = BADGE_TYPE_SITE; @@ -86,6 +88,10 @@ class core_badgeslib_testcase extends advanced_testcase { $fordb->status = BADGE_STATUS_ACTIVE; $this->coursebadge = $DB->insert_record('badge', $fordb, 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"}'; + $this->assertion->class = '{"name":"%s","description":"%s","image":"%s","criteria":"%s","issuer":"%s"}'; + $this->assertion->issuer = '{"name":"%s","url":"%s","email":"%s"}'; } public function test_create_badge() { @@ -103,6 +109,7 @@ class core_badgeslib_testcase extends advanced_testcase { $this->assertEquals($badge->description, $cloned_badge->description); $this->assertEquals($badge->issuercontact, $cloned_badge->issuercontact); $this->assertEquals($badge->issuername, $cloned_badge->issuername); + $this->assertEquals($badge->issuercontact, $cloned_badge->issuercontact); $this->assertEquals($badge->issuerurl, $cloned_badge->issuerurl); $this->assertEquals($badge->expiredate, $cloned_badge->expiredate); $this->assertEquals($badge->expireperiod, $cloned_badge->expireperiod); @@ -279,4 +286,34 @@ class core_badgeslib_testcase extends advanced_testcase { $this->assertDebuggingCalled('Error baking badge image!'); $this->assertTrue($badge->is_issued($this->user->id)); } + + /** + * Test badges assertion generated when a badge is issued. + */ + public function test_badges_assertion() { + $badge = new badge($this->coursebadge); + $this->assertFalse($badge->is_issued($this->user->id)); + + $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id)); + $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY)); + $criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id)); + $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address')); + + $this->user->address = 'Test address'; + user_update_user($this->user, false); + // Check if badge is awarded. + $this->assertDebuggingCalled('Error baking badge image!'); + $awards = $badge->get_awards(); + $this->assertCount(1, $awards); + + // Get assertion. + $award = reset($awards); + $assertion = new core_badges_assertion($award->uniquehash); + $testassertion = $this->assertion; + + // Make sure JSON strings have the same structure. + $this->assertStringMatchesFormat($testassertion->badge, json_encode($assertion->get_badge_assertion())); + $this->assertStringMatchesFormat($testassertion->class, json_encode($assertion->get_badge_class())); + $this->assertStringMatchesFormat($testassertion->issuer, json_encode($assertion->get_issuer())); + } } diff --git a/lib/badgeslib.php b/lib/badgeslib.php index 1a300b94a20..815fb2acfee 100644 --- a/lib/badgeslib.php +++ b/lib/badgeslib.php @@ -821,69 +821,6 @@ function badges_get_user_badges($userid, $courseid = 0, $page = 0, $perpage = 0, return $badges; } -/** - * Get issued badge details for assertion URL - * - * @param string $hash - */ -function badges_get_issued_badge_info($hash) { - global $DB, $CFG; - - $a = array(); - - $record = $DB->get_record_sql(' - SELECT - bi.dateissued, - bi.dateexpire, - u.email, - b.*, - bb.email as backpackemail - FROM - {badge} b - JOIN {badge_issued} bi - ON b.id = bi.badgeid - JOIN {user} u - ON u.id = bi.userid - LEFT JOIN {badge_backpack} bb - ON bb.userid = bi.userid - WHERE ' . $DB->sql_compare_text('bi.uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40), - array('hash' => $hash), IGNORE_MISSING); - - if ($record) { - if ($record->type == BADGE_TYPE_SITE) { - $context = context_system::instance(); - } else { - $context = context_course::instance($record->courseid); - } - - $url = new moodle_url('/badges/badge.php', array('hash' => $hash)); - $email = empty($record->backpackemail) ? $record->email : $record->backpackemail; - - // Recipient's email is hashed: $. - $a['recipient'] = 'sha256$' . hash('sha256', $email . $CFG->badges_badgesalt); - $a['salt'] = $CFG->badges_badgesalt; - - if ($record->dateexpire) { - $a['expires'] = date('Y-m-d', $record->dateexpire); - } - - $a['issued_on'] = date('Y-m-d', $record->dateissued); - $a['evidence'] = $url->out(); // Issued badge URL. - $a['badge'] = array(); - $a['badge']['version'] = '0.5.0'; // Version of OBI specification, 0.5.0 - current beta. - $a['badge']['name'] = $record->name; - $a['badge']['image'] = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $record->id, '/', 'f1')->out(); - $a['badge']['description'] = $record->description; - $a['badge']['criteria'] = $url->out(); // Issued badge URL. - $a['badge']['issuer'] = array(); - $a['badge']['issuer']['origin'] = $record->issuerurl; - $a['badge']['issuer']['name'] = $record->issuername; - $a['badge']['issuer']['contact'] = $record->issuercontact; - } - - return $a; -} - /** * Extends the course administration navigation with the Badges page * diff --git a/lib/deprecatedlib.php b/lib/deprecatedlib.php index 3cc26edb3e2..de60dc4a03e 100644 --- a/lib/deprecatedlib.php +++ b/lib/deprecatedlib.php @@ -4544,4 +4544,17 @@ function get_browser_version_classes() { function generate_email_supportuser() { debugging('generate_email_supportuser is deprecated, please use core_user::get_support_user'); return core_user::get_support_user(); -} \ No newline at end of file +} + +/** + * Get issued badge details for assertion URL + * + * @deprecated since Moodle 2.6 + * @param string $hash Unique hash of a badge + * @return array Information about issued badge. + */ +function badges_get_issued_badge_info($hash) { + debugging('Function badges_get_issued_badge_info() is deprecated. Please use core_badges_assertion class and methods to generate badge assertion.', DEBUG_DEVELOPER); + $assertion = new core_badges_assertion($hash); + return $assertion->get_badge_assertion(); +}