1 |
efrain |
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 |
/**
|
|
|
18 |
* Badge assertion library.
|
|
|
19 |
*
|
|
|
20 |
* @package core
|
|
|
21 |
* @subpackage badges
|
|
|
22 |
* @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
|
|
|
23 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
24 |
* @author Yuliya Bozhko <yuliya.bozhko@totaralms.com>
|
|
|
25 |
*/
|
|
|
26 |
|
|
|
27 |
defined('MOODLE_INTERNAL') || die();
|
|
|
28 |
|
|
|
29 |
/**
|
|
|
30 |
* Open Badges Assertions specification 1.0 {@link https://github.com/mozilla/openbadges-backpack/wiki/Assertions}
|
|
|
31 |
*
|
|
|
32 |
* Badge asserion is defined by three parts:
|
|
|
33 |
* - Badge Assertion (information regarding a specific badge that was awarded to a badge earner)
|
|
|
34 |
* - Badge Class (general information about a badge and what it is intended to represent)
|
|
|
35 |
* - Issuer Class (general information of an issuing organisation)
|
|
|
36 |
*/
|
|
|
37 |
require_once($CFG->libdir . '/badgeslib.php');
|
|
|
38 |
require_once($CFG->dirroot . '/badges/renderer.php');
|
|
|
39 |
|
|
|
40 |
/**
|
|
|
41 |
* Class that represents badge assertion.
|
|
|
42 |
*
|
|
|
43 |
*/
|
|
|
44 |
class core_badges_assertion {
|
|
|
45 |
/** @var object Issued badge information from database */
|
|
|
46 |
private $_data;
|
|
|
47 |
|
|
|
48 |
/** @var moodle_url Issued badge url */
|
|
|
49 |
private $_url;
|
|
|
50 |
|
|
|
51 |
/** @var int $obversion to control version JSON-LD. */
|
|
|
52 |
private $_obversion = OPEN_BADGES_V2;
|
|
|
53 |
|
|
|
54 |
/**
|
|
|
55 |
* Constructs with issued badge unique hash.
|
|
|
56 |
*
|
|
|
57 |
* @param string $hash Badge unique hash from badge_issued table.
|
|
|
58 |
* @param int $obversion to control version JSON-LD.
|
|
|
59 |
*/
|
|
|
60 |
public function __construct($hash, $obversion = OPEN_BADGES_V2) {
|
|
|
61 |
global $DB;
|
|
|
62 |
|
|
|
63 |
$this->_data = $DB->get_record_sql('
|
|
|
64 |
SELECT
|
|
|
65 |
bi.dateissued,
|
|
|
66 |
bi.dateexpire,
|
|
|
67 |
bi.uniquehash,
|
|
|
68 |
u.email,
|
|
|
69 |
b.*,
|
|
|
70 |
bb.email as backpackemail
|
|
|
71 |
FROM
|
|
|
72 |
{badge} b
|
|
|
73 |
JOIN {badge_issued} bi
|
|
|
74 |
ON b.id = bi.badgeid
|
|
|
75 |
JOIN {user} u
|
|
|
76 |
ON u.id = bi.userid
|
|
|
77 |
LEFT JOIN {badge_backpack} bb
|
|
|
78 |
ON bb.userid = bi.userid
|
|
|
79 |
WHERE ' . $DB->sql_compare_text('bi.uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40),
|
|
|
80 |
array('hash' => $hash), IGNORE_MISSING);
|
|
|
81 |
|
|
|
82 |
if ($this->_data) {
|
|
|
83 |
$this->_url = new moodle_url('/badges/badge.php', array('hash' => $this->_data->uniquehash));
|
|
|
84 |
} else {
|
|
|
85 |
$this->_url = new moodle_url('/badges/badge.php');
|
|
|
86 |
}
|
|
|
87 |
$this->_obversion = $obversion;
|
|
|
88 |
}
|
|
|
89 |
|
|
|
90 |
/**
|
|
|
91 |
* Get the local id for this badge.
|
|
|
92 |
*
|
|
|
93 |
* @return int
|
|
|
94 |
*/
|
|
|
95 |
public function get_badge_id() {
|
|
|
96 |
$badgeid = 0;
|
|
|
97 |
if ($this->_data) {
|
|
|
98 |
$badgeid = $this->_data->id;
|
|
|
99 |
}
|
|
|
100 |
return $badgeid;
|
|
|
101 |
}
|
|
|
102 |
|
|
|
103 |
/**
|
|
|
104 |
* Get the local id for this badge assertion.
|
|
|
105 |
*
|
|
|
106 |
* @return string
|
|
|
107 |
*/
|
|
|
108 |
public function get_assertion_hash() {
|
|
|
109 |
$hash = '';
|
|
|
110 |
if ($this->_data) {
|
|
|
111 |
$hash = $this->_data->uniquehash;
|
|
|
112 |
}
|
|
|
113 |
return $hash;
|
|
|
114 |
}
|
|
|
115 |
|
|
|
116 |
/**
|
|
|
117 |
* Get badge assertion.
|
|
|
118 |
*
|
|
|
119 |
* @param boolean $issued Include the nested badge issued information.
|
|
|
120 |
* @param boolean $usesalt Hash the identity and include the salt information for the hash.
|
|
|
121 |
* @return array Badge assertion.
|
|
|
122 |
*/
|
|
|
123 |
public function get_badge_assertion($issued = true, $usesalt = true) {
|
|
|
124 |
global $CFG;
|
|
|
125 |
$assertion = array();
|
|
|
126 |
if ($this->_data) {
|
|
|
127 |
$hash = $this->_data->uniquehash;
|
|
|
128 |
$email = empty($this->_data->backpackemail) ? $this->_data->email : $this->_data->backpackemail;
|
|
|
129 |
$assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
|
|
|
130 |
|
|
|
131 |
if ($this->_obversion >= OPEN_BADGES_V2) {
|
|
|
132 |
$classurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id()));
|
|
|
133 |
} else {
|
|
|
134 |
$classurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'action' => 1));
|
|
|
135 |
}
|
|
|
136 |
|
|
|
137 |
// Required.
|
|
|
138 |
$assertion['uid'] = $hash;
|
|
|
139 |
$assertion['recipient'] = array();
|
|
|
140 |
if ($usesalt) {
|
|
|
141 |
$assertion['recipient']['identity'] = 'sha256$' . hash('sha256', $email . $CFG->badges_badgesalt);
|
|
|
142 |
} else {
|
|
|
143 |
$assertion['recipient']['identity'] = $email;
|
|
|
144 |
}
|
|
|
145 |
$assertion['recipient']['type'] = 'email'; // Currently the only supported type.
|
|
|
146 |
$assertion['recipient']['hashed'] = true; // We are always hashing recipient.
|
|
|
147 |
if ($usesalt) {
|
|
|
148 |
$assertion['recipient']['salt'] = $CFG->badges_badgesalt;
|
|
|
149 |
}
|
|
|
150 |
if ($issued) {
|
|
|
151 |
$assertion['badge'] = $classurl->out(false);
|
|
|
152 |
}
|
|
|
153 |
$assertion['verify'] = array();
|
|
|
154 |
$assertion['verify']['type'] = 'hosted'; // 'Signed' is not implemented yet.
|
|
|
155 |
$assertion['verify']['url'] = $assertionurl->out(false);
|
|
|
156 |
$assertion['issuedOn'] = $this->_data->dateissued;
|
|
|
157 |
if ($issued) {
|
|
|
158 |
$assertion['evidence'] = $this->_url->out(false); // Currently issued badge URL.
|
|
|
159 |
}
|
|
|
160 |
// Optional.
|
|
|
161 |
if (!empty($this->_data->dateexpire)) {
|
|
|
162 |
$assertion['expires'] = $this->_data->dateexpire;
|
|
|
163 |
}
|
|
|
164 |
$tags = $this->get_tags();
|
|
|
165 |
if (is_array($tags) && count($tags) > 0) {
|
|
|
166 |
$assertion['tags'] = $tags;
|
|
|
167 |
}
|
|
|
168 |
$this->embed_data_badge_version2($assertion, OPEN_BADGES_V2_TYPE_ASSERTION);
|
|
|
169 |
}
|
|
|
170 |
return $assertion;
|
|
|
171 |
}
|
|
|
172 |
|
|
|
173 |
/**
|
|
|
174 |
* Get badge class information.
|
|
|
175 |
*
|
|
|
176 |
* @param boolean $issued Include the nested badge issuer information.
|
|
|
177 |
* @return array Badge Class information.
|
|
|
178 |
*/
|
|
|
179 |
public function get_badge_class($issued = true) {
|
|
|
180 |
$class = [];
|
|
|
181 |
if ($this->_data) {
|
|
|
182 |
if (empty($this->_data->courseid)) {
|
|
|
183 |
$context = context_system::instance();
|
|
|
184 |
} else {
|
|
|
185 |
$context = context_course::instance($this->_data->courseid);
|
|
|
186 |
}
|
|
|
187 |
// Required.
|
|
|
188 |
$class['name'] = $this->_data->name;
|
|
|
189 |
$class['description'] = $this->_data->description;
|
|
|
190 |
$storage = get_file_storage();
|
|
|
191 |
$imagefile = $storage->get_file($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f3.png');
|
|
|
192 |
if ($imagefile) {
|
|
|
193 |
$imagedata = base64_encode($imagefile->get_content());
|
|
|
194 |
} else {
|
|
|
195 |
if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
|
|
|
196 |
// Unit tests the file might not exist yet.
|
|
|
197 |
$imagedata = '';
|
|
|
198 |
} else {
|
|
|
199 |
throw new coding_exception('Image file does not exist.');
|
|
|
200 |
}
|
|
|
201 |
}
|
|
|
202 |
$class['image'] = 'data:image/png;base64,' . $imagedata;
|
|
|
203 |
|
|
|
204 |
$params = ['id' => $this->get_badge_id()];
|
|
|
205 |
$badgeurl = new moodle_url('/badges/badgeclass.php', $params);
|
|
|
206 |
$class['criteria'] = $badgeurl->out(false); // Currently badge URL.
|
|
|
207 |
if ($issued) {
|
|
|
208 |
$params = ['id' => $this->get_badge_id(), 'obversion' => $this->_obversion];
|
|
|
209 |
$issuerurl = new moodle_url('/badges/issuer_json.php', $params);
|
|
|
210 |
$class['issuer'] = $issuerurl->out(false);
|
|
|
211 |
}
|
|
|
212 |
$tags = $this->get_tags();
|
|
|
213 |
if (is_array($tags) && count($tags) > 0) {
|
|
|
214 |
$class['tags'] = $tags;
|
|
|
215 |
}
|
|
|
216 |
$this->embed_data_badge_version2($class, OPEN_BADGES_V2_TYPE_BADGE);
|
|
|
217 |
if (!$issued) {
|
|
|
218 |
unset($class['issuer']);
|
|
|
219 |
}
|
|
|
220 |
}
|
|
|
221 |
return $class;
|
|
|
222 |
}
|
|
|
223 |
|
|
|
224 |
/**
|
|
|
225 |
* Get badge issuer information.
|
|
|
226 |
*
|
|
|
227 |
* @return array Issuer information.
|
|
|
228 |
*/
|
|
|
229 |
public function get_issuer() {
|
|
|
230 |
global $CFG;
|
|
|
231 |
$issuer = array();
|
|
|
232 |
if ($this->_data) {
|
|
|
233 |
// Required.
|
|
|
234 |
if ($this->_obversion == OPEN_BADGES_V1) {
|
|
|
235 |
$issuer['name'] = $this->_data->issuername;
|
|
|
236 |
$issuer['url'] = $this->_data->issuerurl;
|
|
|
237 |
// Optional.
|
|
|
238 |
if (!empty($this->_data->issuercontact)) {
|
|
|
239 |
$issuer['email'] = $this->_data->issuercontact;
|
|
|
240 |
} else {
|
|
|
241 |
$issuer['email'] = $CFG->badges_defaultissuercontact;
|
|
|
242 |
}
|
|
|
243 |
} else {
|
|
|
244 |
$badge = new badge($this->get_badge_id());
|
|
|
245 |
$issuer = $badge->get_badge_issuer();
|
|
|
246 |
}
|
|
|
247 |
}
|
|
|
248 |
$this->embed_data_badge_version2($issuer, OPEN_BADGES_V2_TYPE_ISSUER);
|
|
|
249 |
return $issuer;
|
|
|
250 |
}
|
|
|
251 |
|
|
|
252 |
/**
|
|
|
253 |
* Get related badges of the badge.
|
|
|
254 |
*
|
|
|
255 |
* @param badge $badge Badge object.
|
|
|
256 |
* @return array|bool List related badges.
|
|
|
257 |
*/
|
|
|
258 |
public function get_related_badges(badge $badge) {
|
|
|
259 |
global $DB;
|
|
|
260 |
$arraybadges = array();
|
|
|
261 |
$relatedbadges = $badge->get_related_badges(true);
|
|
|
262 |
if ($relatedbadges) {
|
|
|
263 |
foreach ($relatedbadges as $rb) {
|
|
|
264 |
$url = new moodle_url('/badges/badge_json.php', array('id' => $rb->id));
|
|
|
265 |
$arraybadges[] = array(
|
|
|
266 |
'id' => $url->out(false),
|
|
|
267 |
'version' => $rb->version,
|
|
|
268 |
'@language' => $rb->language
|
|
|
269 |
);
|
|
|
270 |
}
|
|
|
271 |
}
|
|
|
272 |
return $arraybadges;
|
|
|
273 |
}
|
|
|
274 |
|
|
|
275 |
/**
|
|
|
276 |
* Get endorsement of the badge.
|
|
|
277 |
*
|
|
|
278 |
* @return false|stdClass Endorsement information.
|
|
|
279 |
*/
|
|
|
280 |
public function get_endorsement() {
|
|
|
281 |
global $DB;
|
|
|
282 |
$endorsement = array();
|
|
|
283 |
$record = $DB->get_record_select('badge_endorsement', 'badgeid = ?', array($this->_data->id));
|
|
|
284 |
return $record;
|
|
|
285 |
}
|
|
|
286 |
|
|
|
287 |
/**
|
|
|
288 |
* Get criteria of badge class.
|
|
|
289 |
*
|
|
|
290 |
* @return array|string Criteria information.
|
|
|
291 |
*/
|
|
|
292 |
public function get_criteria_badge_class() {
|
|
|
293 |
$badge = new badge($this->_data->id);
|
|
|
294 |
$narrative = $badge->markdown_badge_criteria();
|
|
|
295 |
$params = ['id' => $this->get_badge_id()];
|
|
|
296 |
$badgeurl = new moodle_url('/badges/badgeclass.php', $params);
|
|
|
297 |
if (!empty($narrative)) {
|
|
|
298 |
$criteria = [];
|
|
|
299 |
$criteria['id'] = $badgeurl->out(false);
|
|
|
300 |
$criteria['narrative'] = $narrative;
|
|
|
301 |
return $criteria;
|
|
|
302 |
} else {
|
|
|
303 |
return $badgeurl->out(false);
|
|
|
304 |
}
|
|
|
305 |
}
|
|
|
306 |
|
|
|
307 |
/**
|
|
|
308 |
* Get alignment of the badge.
|
|
|
309 |
*
|
|
|
310 |
* @return array information.
|
|
|
311 |
*/
|
|
|
312 |
public function get_alignments() {
|
|
|
313 |
global $DB;
|
|
|
314 |
$badgeid = $this->_data->id;
|
|
|
315 |
$alignments = array();
|
|
|
316 |
$items = $DB->get_records_select('badge_alignment', 'badgeid = ?', array($badgeid));
|
|
|
317 |
foreach ($items as $item) {
|
|
|
318 |
$alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
|
|
|
319 |
if ($item->targetdescription) {
|
|
|
320 |
$alignment['targetDescription'] = $item->targetdescription;
|
|
|
321 |
}
|
|
|
322 |
if ($item->targetframework) {
|
|
|
323 |
$alignment['targetFramework'] = $item->targetframework;
|
|
|
324 |
}
|
|
|
325 |
if ($item->targetcode) {
|
|
|
326 |
$alignment['targetCode'] = $item->targetcode;
|
|
|
327 |
}
|
|
|
328 |
$alignments[] = $alignment;
|
|
|
329 |
}
|
|
|
330 |
return $alignments;
|
|
|
331 |
}
|
|
|
332 |
|
|
|
333 |
/**
|
|
|
334 |
* Embed data of Open Badges Specification Version 2.0 to json.
|
|
|
335 |
*
|
|
|
336 |
* @param array $json for assertion, badges, issuer.
|
|
|
337 |
* @param string $type Content type.
|
|
|
338 |
*/
|
|
|
339 |
protected function embed_data_badge_version2(&$json, $type = OPEN_BADGES_V2_TYPE_ASSERTION) {
|
|
|
340 |
// Specification Version 2.0.
|
|
|
341 |
if ($this->_obversion >= OPEN_BADGES_V2) {
|
|
|
342 |
$badge = new badge($this->_data->id);
|
|
|
343 |
if (empty($this->_data->courseid)) {
|
|
|
344 |
$context = context_system::instance();
|
|
|
345 |
} else {
|
|
|
346 |
$context = context_course::instance($this->_data->courseid);
|
|
|
347 |
}
|
|
|
348 |
|
|
|
349 |
$hash = $this->_data->uniquehash;
|
|
|
350 |
$assertionsurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
|
|
|
351 |
$classurl = new moodle_url(
|
|
|
352 |
'/badges/badge_json.php',
|
|
|
353 |
array('id' => $this->get_badge_id())
|
|
|
354 |
);
|
|
|
355 |
$issuerurl = new moodle_url('/badges/issuer_json.php', ['id' => $this->get_badge_id()]);
|
|
|
356 |
// For assertion.
|
|
|
357 |
if ($type == OPEN_BADGES_V2_TYPE_ASSERTION) {
|
|
|
358 |
$json['@context'] = OPEN_BADGES_V2_CONTEXT;
|
|
|
359 |
$json['type'] = OPEN_BADGES_V2_TYPE_ASSERTION;
|
|
|
360 |
$json['id'] = $assertionsurl->out(false);
|
|
|
361 |
$json['badge'] = $this->get_badge_class();
|
|
|
362 |
$json['issuedOn'] = date('c', $this->_data->dateissued);
|
|
|
363 |
if (!empty($this->_data->dateexpire)) {
|
|
|
364 |
$json['expires'] = date('c', $this->_data->dateexpire);
|
|
|
365 |
}
|
|
|
366 |
unset($json['uid']);
|
|
|
367 |
}
|
|
|
368 |
// For Badge.
|
|
|
369 |
if ($type == OPEN_BADGES_V2_TYPE_BADGE) {
|
|
|
370 |
$json['@context'] = OPEN_BADGES_V2_CONTEXT;
|
|
|
371 |
$json['id'] = $classurl->out(false);
|
|
|
372 |
$json['type'] = OPEN_BADGES_V2_TYPE_BADGE;
|
|
|
373 |
$json['version'] = $this->_data->version;
|
|
|
374 |
$json['criteria'] = $this->get_criteria_badge_class();
|
|
|
375 |
$json['issuer'] = $this->get_issuer();
|
|
|
376 |
$json['@language'] = $this->_data->language;
|
|
|
377 |
if (!empty($relatedbadges = $this->get_related_badges($badge))) {
|
|
|
378 |
$json['related'] = $relatedbadges;
|
|
|
379 |
}
|
|
|
380 |
if ($endorsement = $this->get_endorsement()) {
|
|
|
381 |
$endorsementurl = new moodle_url('/badges/endorsement_json.php', array('id' => $this->_data->id));
|
|
|
382 |
$json['endorsement'] = $endorsementurl->out(false);
|
|
|
383 |
}
|
|
|
384 |
if ($alignments = $this->get_alignments()) {
|
|
|
385 |
$json['alignments'] = $alignments;
|
|
|
386 |
}
|
|
|
387 |
if ($this->_data->imageauthorname ||
|
|
|
388 |
$this->_data->imageauthoremail ||
|
|
|
389 |
$this->_data->imageauthorurl ||
|
|
|
390 |
$this->_data->imagecaption) {
|
|
|
391 |
$storage = get_file_storage();
|
|
|
392 |
$imagefile = $storage->get_file($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f3.png');
|
|
|
393 |
if ($imagefile) {
|
|
|
394 |
$imagedata = base64_encode($imagefile->get_content());
|
|
|
395 |
} else {
|
|
|
396 |
// The file might not exist in unit tests.
|
|
|
397 |
if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
|
|
|
398 |
$imagedata = '';
|
|
|
399 |
} else {
|
|
|
400 |
throw new coding_exception('Image file does not exist.');
|
|
|
401 |
}
|
|
|
402 |
}
|
|
|
403 |
$json['image'] = 'data:image/png;base64,' . $imagedata;
|
|
|
404 |
}
|
|
|
405 |
}
|
|
|
406 |
|
|
|
407 |
// For issuer.
|
|
|
408 |
if ($type == OPEN_BADGES_V2_TYPE_ISSUER) {
|
|
|
409 |
$json['@context'] = OPEN_BADGES_V2_CONTEXT;
|
|
|
410 |
$json['id'] = $issuerurl->out(false);
|
|
|
411 |
$json['type'] = OPEN_BADGES_V2_TYPE_ISSUER;
|
|
|
412 |
}
|
|
|
413 |
}
|
|
|
414 |
}
|
|
|
415 |
|
|
|
416 |
/**
|
|
|
417 |
* Get tags of the badge.
|
|
|
418 |
*
|
|
|
419 |
* @return array tags.
|
|
|
420 |
*/
|
|
|
421 |
public function get_tags(): array {
|
|
|
422 |
return array_values(\core_tag_tag::get_item_tags_array('core_badges', 'badge', $this->get_badge_id()));
|
|
|
423 |
}
|
|
|
424 |
}
|