Merge branch 'MDL-68286-master' of git://github.com/andrewnicols/moodle
[moodle.git] / badges / classes / assertion.php
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/>.
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  */
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * Open Badges Assertions specification 1.0 {@link https://github.com/mozilla/openbadges/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');
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;
48     /** @var moodle_url Issued badge url */
49     private $_url;
51     /** @var int $obversion to control version JSON-LD. */
52     private $_obversion = OPEN_BADGES_V1;
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_V1) {
61         global $DB;
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);
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     }
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     }
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     }
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));
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             }
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             $this->embed_data_badge_version2($assertion, OPEN_BADGES_V2_TYPE_ASSERTION);
165         }
166         return $assertion;
167     }
169     /**
170      * Get badge class information.
171      *
172      * @param boolean $issued Include the nested badge issuer information.
173      * @return array Badge Class information.
174      */
175     public function get_badge_class($issued = true) {
176         $class = [];
177         if ($this->_data) {
178             if (empty($this->_data->courseid)) {
179                 $context = context_system::instance();
180             } else {
181                 $context = context_course::instance($this->_data->courseid);
182             }
183             // Required.
184             $class['name'] = $this->_data->name;
185             $class['description'] = $this->_data->description;
186             $storage = get_file_storage();
187             $imagefile = $storage->get_file($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f3.png');
188             if ($imagefile) {
189                 $imagedata = base64_encode($imagefile->get_content());
190             } else {
191                 if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
192                     // Unit tests the file might not exist yet.
193                     $imagedata = '';
194                 } else {
195                     throw new coding_exception('Image file does not exist.');
196                 }
197             }
198             $class['image'] = 'data:image/png;base64,' . $imagedata;
199             $class['criteria'] = $this->_url->out(false); // Currently issued badge URL.
200             if ($issued) {
201                 if ($this->_obversion == OPEN_BADGES_V2) {
202                     $issuerurl = new moodle_url('/badges/issuer_json.php', array('id' => $this->get_badge_id()));
203                 } else {
204                     $issuerurl = new moodle_url('/badges/assertion.php', array('b' => $this->_data->uniquehash, 'action' => 0));
205                 }
206                 $class['issuer'] = $issuerurl->out(false);
207             }
208             $this->embed_data_badge_version2($class, OPEN_BADGES_V2_TYPE_BADGE);
209             if (!$issued) {
210                 unset($class['issuer']);
211             }
212         }
213         return $class;
214     }
216     /**
217      * Get badge issuer information.
218      *
219      * @return array Issuer information.
220      */
221     public function get_issuer() {
222         global $CFG;
223         $issuer = array();
224         if ($this->_data) {
225             // Required.
226             if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) {
227                 $issuer['name'] = $this->_data->issuername;
228                 $issuer['url'] = $this->_data->issuerurl;
229                 // Optional.
230                 if (!empty($this->_data->issuercontact)) {
231                     $issuer['email'] = $this->_data->issuercontact;
232                 } else {
233                     $issuer['email'] = $CFG->badges_defaultissuercontact;
234                 }
235             } else {
236                 $badge = new badge($this->get_badge_id());
237                 $issuer = $badge->get_badge_issuer();
238             }
239         }
240         $this->embed_data_badge_version2($issuer, OPEN_BADGES_V2_TYPE_ISSUER);
241         return $issuer;
242     }
244     /**
245      * Get related badges of the badge.
246      *
247      * @param badge $badge Badge object.
248      * @return array|bool List related badges.
249      */
250     public function get_related_badges(badge $badge) {
251         global $DB;
252         $arraybadges = array();
253         $relatedbadges = $badge->get_related_badges(true);
254         if ($relatedbadges) {
255             foreach ($relatedbadges as $rb) {
256                 $url = new moodle_url('/badges/badge_json.php', array('id' => $rb->id));
257                 $arraybadges[] = array(
258                     'id'        => $url->out(false),
259                     'version'   => $rb->version,
260                     '@language' => $rb->language
261                 );
262             }
263         }
264         return $arraybadges;
265     }
267     /**
268      * Get endorsement of the badge.
269      *
270      * @return false|stdClass Endorsement information.
271      */
272     public function get_endorsement() {
273         global $DB;
274         $endorsement = array();
275         $record = $DB->get_record_select('badge_endorsement', 'badgeid = ?', array($this->_data->id));
276         return $record;
277     }
279     /**
280      * Get criteria of badge class.
281      *
282      * @return array|string Criteria information.
283      */
284     public function get_criteria_badge_class() {
285         $badge = new badge($this->_data->id);
286         $narrative = $badge->markdown_badge_criteria();
287         if (!empty($narrative)) {
288             $criteria = array();
289             $criteria['id'] = $this->_url->out(false);
290             $criteria['narrative'] = $narrative;
291             return $criteria;
292         } else {
293             return $this->_url->out(false);
294         }
295     }
297     /**
298      * Get alignment of the badge.
299      *
300      * @return array information.
301      */
302     public function get_alignments() {
303         global $DB;
304         $badgeid = $this->_data->id;
305         $alignments = array();
306         $items = $DB->get_records_select('badge_alignment', 'badgeid = ?', array($badgeid));
307         foreach ($items as $item) {
308             $alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
309             if ($item->targetdescription) {
310                 $alignment['targetDescription'] = $item->targetdescription;
311             }
312             if ($item->targetframework) {
313                 $alignment['targetFramework'] = $item->targetframework;
314             }
315             if ($item->targetcode) {
316                 $alignment['targetCode'] = $item->targetcode;
317             }
318             $alignments[] = $alignment;
319         }
320         return $alignments;
321     }
323     /**
324      * Embed data of Open Badges Specification Version 2.0 to json.
325      *
326      * @param array $json for assertion, badges, issuer.
327      * @param string $type Content type.
328      */
329     protected function embed_data_badge_version2 (&$json, $type = OPEN_BADGES_V2_TYPE_ASSERTION) {
330         // Specification Version 2.0.
331         if ($this->_obversion == OPEN_BADGES_V2) {
332             $badge = new badge($this->_data->id);
333             if (empty($this->_data->courseid)) {
334                 $context = context_system::instance();
335             } else {
336                 $context = context_course::instance($this->_data->courseid);
337             }
339             $hash = $this->_data->uniquehash;
340             $assertionsurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
341             $classurl = new moodle_url(
342                 '/badges/badge_json.php',
343                 array('id' => $this->get_badge_id())
344             );
345             $issuerurl = new moodle_url('/badges/issuer_json.php', ['id' => $this->get_badge_id()]);
346             // For assertion.
347             if ($type == OPEN_BADGES_V2_TYPE_ASSERTION) {
348                 $json['@context'] = OPEN_BADGES_V2_CONTEXT;
349                 $json['type'] = OPEN_BADGES_V2_TYPE_ASSERTION;
350                 $json['id'] = $assertionsurl->out(false);
351                 $json['badge'] = $this->get_badge_class();
352                 $json['issuedOn'] = date('c', $this->_data->dateissued);
353                 if (!empty($this->_data->dateexpire)) {
354                     $json['expires'] = date('c', $this->_data->dateexpire);
355                 }
356                 unset($json['uid']);
357             }
358             // For Badge.
359             if ($type == OPEN_BADGES_V2_TYPE_BADGE) {
360                 $json['@context'] = OPEN_BADGES_V2_CONTEXT;
361                 $json['id'] = $classurl->out(false);
362                 $json['type'] = OPEN_BADGES_V2_TYPE_BADGE;
363                 $json['version'] = $this->_data->version;
364                 $json['criteria'] = $this->get_criteria_badge_class();
365                 $json['issuer'] = $this->get_issuer();
366                 $json['@language'] = $this->_data->language;
367                 if (!empty($relatedbadges = $this->get_related_badges($badge))) {
368                     $json['related'] = $relatedbadges;
369                 }
370                 if ($endorsement = $this->get_endorsement()) {
371                     $endorsementurl = new moodle_url('/badges/endorsement_json.php', array('id' => $this->_data->id));
372                     $json['endorsement'] = $endorsementurl->out(false);
373                 }
374                 if ($alignments = $this->get_alignments()) {
375                     $json['alignments'] = $alignments;
376                 }
377                 if ($this->_data->imageauthorname ||
378                         $this->_data->imageauthoremail ||
379                         $this->_data->imageauthorurl ||
380                         $this->_data->imagecaption) {
381                     $storage = get_file_storage();
382                     $imagefile = $storage->get_file($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f1.png');
383                     if ($imagefile) {
384                         $imagedata = base64_encode($imagefile->get_content());
385                     } else {
386                         // The file might not exist in unit tests.
387                         if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
388                             $imagedata = '';
389                         } else {
390                             throw new coding_exception('Image file does not exist.');
391                         }
392                     }
393                     $json['image'] = 'data:image/png;base64,' . $imagedata;
394                 }
395             }
397             // For issuer.
398             if ($type == OPEN_BADGES_V2_TYPE_ISSUER) {
399                 $json['@context'] = OPEN_BADGES_V2_CONTEXT;
400                 $json['id'] = $issuerurl->out(false);
401                 $json['type'] = OPEN_BADGES_V2_TYPE_ISSUER;
402             }
403         }
404     }