Skip to content

Commit 8211535

Browse files
committed
Merge backpack-add and backpack-export
1 parent 016613a commit 8211535

6 files changed

+223
-118
lines changed

badges/backpack-add.php

+25-117
Original file line numberDiff line numberDiff line change
@@ -29,130 +29,38 @@
2929

3030
require_login();
3131

32-
$userbackpack = badges_get_user_backpack();
33-
if (badges_open_badges_backpack_api($userbackpack->id) != OPEN_BADGES_V2) {
34-
throw new coding_exception('No backpacks support Open Badges V2.');
32+
// Check if badges and the external backpack are enabled.
33+
if (empty($CFG->badges_allowexternalbackpack) || empty($CFG->enablebadges)) {
34+
redirect($CFG->wwwroot);
3535
}
3636

37-
$id = required_param('hash', PARAM_ALPHANUM);
38-
39-
$PAGE->set_url('/badges/backpack-add.php', array('hash' => $id));
40-
$PAGE->set_context(context_system::instance());
41-
$output = $PAGE->get_renderer('core', 'badges');
42-
43-
$issuedbadge = new \core_badges\output\issued_badge($id);
44-
if (!empty($issuedbadge->recipient->id)) {
45-
// The flow for issuing a badge is:
46-
// * Create issuer
47-
// * Create badge
48-
// * Create assertion (Award the badge!)
49-
50-
// With the introduction OBv2.1 and MDL-65959 to allow cross region Badgr imports the above (old) procedure will
51-
// only be completely performed if both the site and user backpacks conform to the same apiversion.
52-
// Else we will attempt at pushing the assertion to the user's backpack. In this case, the id set against the assertion
53-
// has to be a publicly accessible resource.
37+
// Check the user has a backpack.
38+
$backpack = \core_badges\backpack::get_user_backpack();
39+
if (empty($backpack)) {
40+
throw new coding_exception('This user has no backpack associated with their account.');
41+
}
5442

55-
// Get the backpack.
56-
$badgeid = $issuedbadge->badgeid;
57-
$badge = new badge($badgeid);
58-
$backpack = $DB->get_record('badge_backpack', array('userid' => $USER->id));
59-
$userbackpack = badges_get_site_backpack($backpack->externalbackpackid, $USER->id);
60-
$assertion = new core_badges_assertion($id, OPEN_BADGES_V2);
61-
$assertiondata = $assertion->get_badge_assertion(false, false);
62-
$assertionid = $assertion->get_assertion_hash();
63-
$assertionentityid = $assertiondata['id'];
64-
$badgeadded = false;
65-
if (badges_open_badges_backpack_api() == OPEN_BADGES_V2) {
66-
$sitebackpack = badges_get_site_primary_backpack();
67-
$api = backpackapi_base::create_from_externalbackpack($userbackpack);
68-
$response = $api->authenticate();
43+
$hash = required_param('hash', PARAM_ALPHANUM);
6944

70-
// A numeric response indicates a valid successful authentication. Else an error object will be returned.
71-
if (is_numeric($response)) {
72-
// Create issuer.
73-
$issuer = $assertion->get_issuer();
74-
if (!($issuerentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email']))) {
75-
$response = $api->put_issuer($issuer);
76-
if (!$response) {
77-
throw new moodle_exception('invalidrequest', 'error');
78-
}
79-
$issuerentityid = $response->id;
80-
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email'],
81-
$issuerentityid);
82-
}
83-
// Create badge.
84-
$badge = $assertion->get_badge_class(false);
85-
$badgeid = $assertion->get_badge_id();
86-
if (!($badgeentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid))) {
87-
$response = $api->put_badgeclass($issuerentityid, $badge);
88-
if (!$response) {
89-
throw new moodle_exception('invalidrequest', 'error');
90-
}
91-
$badgeentityid = $response->id;
92-
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid,
93-
$badgeentityid);
94-
}
45+
$PAGE->set_url('/badges/backpack-add.php', ['hash' => $hash]);
46+
$PAGE->set_context(context_system::instance()); // TODO: System or user context?
47+
$output = $PAGE->get_renderer('core', 'badges');
9548

96-
// Create assertion (Award the badge!).
97-
$assertionentityid = badges_external_get_mapping(
98-
$sitebackpack->id,
99-
OPEN_BADGES_V2_TYPE_ASSERTION,
100-
$assertionid
101-
);
49+
// Check the assertion belongs to the current user.
50+
$assertion = new core_badges_assertion($hash, $backpack->apiversion);
51+
if ($assertion->get_userid() != $USER->id) {
52+
throw new coding_exception('This assertion does not belong to the current user.');
53+
}
10254

103-
if ($assertionentityid && strpos($sitebackpack->backpackapiurl, 'badgr')) {
104-
$assertionentityid = badges_generate_badgr_open_url(
105-
$sitebackpack,
106-
OPEN_BADGES_V2_TYPE_ASSERTION,
107-
$assertionentityid
108-
);
109-
}
11055

111-
// Create an assertion for the recipient in the issuer's account.
112-
if (!$assertionentityid) {
113-
$response = $api->put_badgeclass_assertion($badgeentityid, $assertiondata);
114-
if (!$response) {
115-
throw new moodle_exception('invalidrequest', 'error');
116-
}
117-
$assertionentityid = badges_generate_badgr_open_url($sitebackpack, OPEN_BADGES_V2_TYPE_ASSERTION, $response->id);
118-
$badgeadded = true;
119-
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
120-
$response->id);
121-
} else {
122-
// An assertion already exists. Make sure it's up to date.
123-
$internalid = badges_external_get_mapping(
124-
$sitebackpack->id,
125-
OPEN_BADGES_V2_TYPE_ASSERTION,
126-
$assertionid,
127-
'externalid'
128-
);
129-
$response = $api->update_assertion($internalid, $assertiondata);
130-
if (!$response) {
131-
throw new moodle_exception('invalidrequest', 'error');
132-
}
133-
}
134-
}
135-
}
56+
// TODO: Improve code for OBv2.0. It's not working (or maybe it is but needs to be tested with ngrok) because I copied the code from the old version. OBv2.1 is working.
13657

137-
// Now award/upload the badge to the user's account.
138-
// - If a user and site backpack have the same provider we can skip this as Badgr automatically maps recipients
139-
// based on email address.
140-
// - This is only needed when the backpacks are from different regions.
141-
if ($assertionentityid && !badges_external_get_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid)) {
142-
$userapi = backpackapi_base::create_from_externalbackpack($userbackpack);
143-
$userapi->authenticate();
144-
$response = $userapi->import_badge_assertion($assertionentityid);
145-
if (!$response) {
146-
throw new moodle_exception('invalidrequest', 'error');
147-
}
148-
$assertionentityid = $response->id;
149-
$badgeadded = true;
150-
badges_external_create_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
151-
$assertionentityid);
152-
}
58+
// Send the assertion to the backpack.
59+
$api = backpackapi_base::create_from_externalbackpack($backpack);
60+
$notify = $api->put_assertions($hash);
15361

154-
$response = $badgeadded ? ['success' => 'addedtobackpack'] : ['warning' => 'existsinbackpack'];
155-
redirect(new moodle_url('/badges/mybadges.php', $response));
156-
} else {
157-
redirect(new moodle_url('/badges/mybadges.php'));
62+
$redirecturl = new moodle_url('/badges/mybadges.php');
63+
if (!empty($notify['status'])) {
64+
redirect($redirecturl, $notify['message'], null, $notify['status']);
15865
}
66+
redirect($redirecturl);

badges/classes/assertion.php

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public function __construct($hash, $obversion = OPEN_BADGES_V2) {
6161
bi.dateexpire,
6262
bi.uniquehash,
6363
u.email,
64+
u.id as userid,
6465
b.*,
6566
bb.email as backpackemail
6667
FROM
@@ -401,4 +402,8 @@ protected function embed_data_badge_version2(&$json, $type = OPEN_BADGES_V2_TYPE
401402
public function get_tags(): array {
402403
return array_values(\core_tag_tag::get_item_tags_array('core_badges', 'badge', $this->get_badge_id()));
403404
}
405+
406+
public function get_userid(): int {
407+
return $this->_data->userid;
408+
}
404409
}

badges/classes/backpack.php

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
namespace core_badges;
18+
19+
/**
20+
* Class that represents a backpack.
21+
*
22+
* @package core_badges
23+
* @copyright 2024 Sara Arjona <sara@moodle.com>
24+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25+
*/
26+
class backpack {
27+
28+
/**
29+
* Constructs with badge details.
30+
*
31+
* @param int $badgeid badge ID.
32+
*/
33+
public function __construct(string $hash) {
34+
}
35+
36+
37+
/**
38+
* Get the user backpack for the currently logged in user OR the provided user
39+
*
40+
* @param int|null $userid The user whose backpack you're requesting for. If null, get the logged in user's backpack
41+
* @return mixed The user's backpack or none.
42+
*/
43+
public static function get_user_backpack(?int $userid = 0) {
44+
global $DB;
45+
46+
if (!$userid) {
47+
global $USER;
48+
$userid = $USER->id;
49+
}
50+
51+
$sql = "SELECT beb.*, bb.id AS badgebackpack, bb.password, bb.email AS backpackemail
52+
FROM {badge_external_backpack} beb
53+
JOIN {badge_backpack} bb ON bb.externalbackpackid = beb.id AND bb.userid=:userid";
54+
55+
return $DB->get_record_sql($sql, ['userid' => $userid]);
56+
}
57+
58+
}

badges/classes/backpackapi_2p0.php

+126
Original file line numberDiff line numberDiff line change
@@ -510,4 +510,130 @@ public function get_badges($collection, $expanded = false) {
510510

511511
return [];
512512
}
513+
514+
/**
515+
* Send an assertion to the backpack.
516+
*
517+
* @param string $hash The assertion hash
518+
* @return array
519+
*/
520+
public function put_assertions(string $hash): array {
521+
global $USER, $DB;
522+
523+
$issuedbadge = new \core_badges\output\issued_badge($hash);
524+
if (!empty($issuedbadge->recipient->id)) {
525+
// The flow for issuing a badge is:
526+
// * Create issuer
527+
// * Create badge
528+
// * Create assertion (Award the badge!)
529+
530+
// With the introduction OBv2.1 and MDL-65959 to allow cross region Badgr imports the above (old) procedure will
531+
// only be completely performed if both the site and user backpacks conform to the same apiversion.
532+
// Else we will attempt at pushing the assertion to the user's backpack. In this case, the id set against the assertion
533+
// has to be a publicly accessible resource.
534+
535+
// Get the backpack.
536+
$badgeid = $issuedbadge->badgeid;
537+
$badge = new badge($badgeid);
538+
$backpack = $DB->get_record('badge_backpack', array('userid' => $USER->id));
539+
$userbackpack = badges_get_site_backpack($backpack->externalbackpackid, $USER->id);
540+
$assertion = new \core_badges_assertion($hash, OPEN_BADGES_V2);
541+
$assertiondata = $assertion->get_badge_assertion(false, false);
542+
$assertionid = $assertion->get_assertion_hash();
543+
$assertionentityid = $assertiondata['id'];
544+
$badgeadded = false;
545+
if (badges_open_badges_backpack_api() == OPEN_BADGES_V2) {
546+
$sitebackpack = badges_get_site_primary_backpack();
547+
$api = backpackapi_base::create_from_externalbackpack($userbackpack);
548+
$response = $api->authenticate();
549+
550+
// A numeric response indicates a valid successful authentication. Else an error object will be returned.
551+
if (is_numeric($response)) {
552+
// Create issuer.
553+
$issuer = $assertion->get_issuer();
554+
if (!($issuerentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email']))) {
555+
$response = $api->put_issuer($issuer);
556+
if (!$response) {
557+
throw new \moodle_exception('invalidrequest', 'error');
558+
}
559+
$issuerentityid = $response->id;
560+
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email'],
561+
$issuerentityid);
562+
}
563+
// Create badge.
564+
$badge = $assertion->get_badge_class(false);
565+
$badgeid = $assertion->get_badge_id();
566+
if (!($badgeentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid))) {
567+
$response = $api->put_badgeclass($issuerentityid, $badge);
568+
if (!$response) {
569+
throw new \moodle_exception('invalidrequest', 'error');
570+
}
571+
$badgeentityid = $response->id;
572+
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid,
573+
$badgeentityid);
574+
}
575+
576+
// Create assertion (Award the badge!).
577+
$assertionentityid = badges_external_get_mapping(
578+
$sitebackpack->id,
579+
OPEN_BADGES_V2_TYPE_ASSERTION,
580+
$assertionid
581+
);
582+
583+
if ($assertionentityid && strpos($sitebackpack->backpackapiurl, 'badgr')) {
584+
$assertionentityid = badges_generate_badgr_open_url(
585+
$sitebackpack,
586+
OPEN_BADGES_V2_TYPE_ASSERTION,
587+
$assertionentityid
588+
);
589+
}
590+
591+
// Create an assertion for the recipient in the issuer's account.
592+
if (!$assertionentityid) {
593+
$response = $api->put_badgeclass_assertion($badgeentityid, $assertiondata);
594+
if (!$response) {
595+
throw new \moodle_exception('invalidrequest', 'error');
596+
}
597+
$assertionentityid = badges_generate_badgr_open_url($sitebackpack, OPEN_BADGES_V2_TYPE_ASSERTION, $response->id);
598+
$badgeadded = true;
599+
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
600+
$response->id);
601+
} else {
602+
// An assertion already exists. Make sure it's up to date.
603+
$internalid = badges_external_get_mapping(
604+
$sitebackpack->id,
605+
OPEN_BADGES_V2_TYPE_ASSERTION,
606+
$assertionid,
607+
'externalid'
608+
);
609+
$response = $api->update_assertion($internalid, $assertiondata);
610+
if (!$response) {
611+
throw new \moodle_exception('invalidrequest', 'error');
612+
}
613+
}
614+
}
615+
}
616+
617+
// Now award/upload the badge to the user's account.
618+
// - If a user and site backpack have the same provider we can skip this as Badgr automatically maps recipients
619+
// based on email address.
620+
// - This is only needed when the backpacks are from different regions.
621+
if ($assertionentityid && !badges_external_get_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid)) {
622+
$userapi = backpackapi_base::create_from_externalbackpack($userbackpack);
623+
$userapi->authenticate();
624+
$response = $userapi->import_badge_assertion($assertionentityid);
625+
if (!$response) {
626+
throw new \moodle_exception('invalidrequest', 'error');
627+
}
628+
$assertionentityid = $response->id;
629+
$badgeadded = true;
630+
badges_external_create_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
631+
$assertionentityid);
632+
}
633+
634+
$response = $badgeadded ? ['success' => 'addedtobackpack'] : ['warning' => 'existsinbackpack'];
635+
}
636+
637+
return $response;
638+
}
513639
}

badges/classes/backpackapi_2p1.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ private function get_clientid($issuerid) {
233233
* @throws \moodle_exception
234234
* @throws coding_exception
235235
*/
236-
public function put_assertions($hash) {
236+
public function put_assertions(string $hash): array {
237237
$data = [];
238238
if (!$hash) {
239239
return false;

badges/classes/backpackapi_base.php

+8
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ abstract protected function get_mappings(): array;
8585
*/
8686
abstract public function disconnect_backpack(): bool;
8787

88+
/**
89+
* Send an assertion to the backpack.
90+
*
91+
* @param string $hash The assertion hash
92+
* @return array
93+
*/
94+
abstract public function put_assertions(string $hash): array;
95+
8896
/**
8997
* Create a new backpackapi instance from an external backpack record.
9098
*

0 commit comments

Comments
 (0)