Merge branch 'MDL-66357-master' of git://github.com/sarjona/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_V2;
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;
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                 $params = ['id' => $this->get_badge_id(), 'obversion' => $this->_obversion];
202                 $issuerurl = new moodle_url('/badges/issuer_json.php', $params);
203                 $class['issuer'] = $issuerurl->out(false);
204             }
205             $this->embed_data_badge_version2($class, OPEN_BADGES_V2_TYPE_BADGE);
206             if (!$issued) {
207                 unset($class['issuer']);
208             }
209         }
210         return $class;
211     }
213     /**
214      * Get badge issuer information.
215      *
216      * @return array Issuer information.
217      */
218     public function get_issuer() {
219         global $CFG;
220         $issuer = array();
221         if ($this->_data) {
222             // Required.
223             if ($this->_obversion == OPEN_BADGES_V1) {
224                 $issuer['name'] = $this->_data->issuername;
225                 $issuer['url'] = $this->_data->issuerurl;
226                 // Optional.
227                 if (!empty($this->_data->issuercontact)) {
228                     $issuer['email'] = $this->_data->issuercontact;
229                 } else {
230                     $issuer['email'] = $CFG->badges_defaultissuercontact;
231                 }
232             } else {
233                 $badge = new badge($this->get_badge_id());
234                 $issuer = $badge->get_badge_issuer();
235             }
236         }
237         $this->embed_data_badge_version2($issuer, OPEN_BADGES_V2_TYPE_ISSUER);
238         return $issuer;
239     }
241     /**
242      * Get related badges of the badge.
243      *
244      * @param badge $badge Badge object.
245      * @return array|bool List related badges.
246      */
247     public function get_related_badges(badge $badge) {
248         global $DB;
249         $arraybadges = array();
250         $relatedbadges = $badge->get_related_badges(true);
251         if ($relatedbadges) {
252             foreach ($relatedbadges as $rb) {
253                 $url = new moodle_url('/badges/badge_json.php', array('id' => $rb->id));
254                 $arraybadges[] = array(
255                     'id'        => $url->out(false),
256                     'version'   => $rb->version,
257                     '@language' => $rb->language
258                 );
259             }
260         }
261         return $arraybadges;
262     }
264     /**
265      * Get endorsement of the badge.
266      *
267      * @return false|stdClass Endorsement information.
268      */
269     public function get_endorsement() {
270         global $DB;
271         $endorsement = array();
272         $record = $DB->get_record_select('badge_endorsement', 'badgeid = ?', array($this->_data->id));
273         return $record;
274     }
276     /**
277      * Get criteria of badge class.
278      *
279      * @return array|string Criteria information.
280      */
281     public function get_criteria_badge_class() {
282         $badge = new badge($this->_data->id);
283         $narrative = $badge->markdown_badge_criteria();
284         if (!empty($narrative)) {
285             $criteria = array();
286             $criteria['id'] = $this->_url->out(false);
287             $criteria['narrative'] = $narrative;
288             return $criteria;
289         } else {
290             return $this->_url->out(false);
291         }
292     }
294     /**
295      * Get alignment of the badge.
296      *
297      * @return array information.
298      */
299     public function get_alignments() {
300         global $DB;
301         $badgeid = $this->_data->id;
302         $alignments = array();
303         $items = $DB->get_records_select('badge_alignment', 'badgeid = ?', array($badgeid));
304         foreach ($items as $item) {
305             $alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
306             if ($item->targetdescription) {
307                 $alignment['targetDescription'] = $item->targetdescription;
308             }
309             if ($item->targetframework) {
310                 $alignment['targetFramework'] = $item->targetframework;
311             }
312             if ($item->targetcode) {
313                 $alignment['targetCode'] = $item->targetcode;
314             }
315             $alignments[] = $alignment;
316         }
317         return $alignments;
318     }
320     /**
321      * Embed data of Open Badges Specification Version 2.0 to json.
322      *
323      * @param array $json for assertion, badges, issuer.
324      * @param string $type Content type.
325      */
326     protected function embed_data_badge_version2 (&$json, $type = OPEN_BADGES_V2_TYPE_ASSERTION) {
327         // Specification Version 2.0.
328         if ($this->_obversion == OPEN_BADGES_V2) {
329             $badge = new badge($this->_data->id);
330             if (empty($this->_data->courseid)) {
331                 $context = context_system::instance();
332             } else {
333                 $context = context_course::instance($this->_data->courseid);
334             }
336             $hash = $this->_data->uniquehash;
337             $assertionsurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
338             $classurl = new moodle_url(
339                 '/badges/badge_json.php',
340                 array('id' => $this->get_badge_id())
341             );
342             $issuerurl = new moodle_url('/badges/issuer_json.php', ['id' => $this->get_badge_id()]);
343             // For assertion.
344             if ($type == OPEN_BADGES_V2_TYPE_ASSERTION) {
345                 $json['@context'] = OPEN_BADGES_V2_CONTEXT;
346                 $json['type'] = OPEN_BADGES_V2_TYPE_ASSERTION;
347                 $json['id'] = $assertionsurl->out(false);
348                 $json['badge'] = $this->get_badge_class();
349                 $json['issuedOn'] = date('c', $this->_data->dateissued);
350                 if (!empty($this->_data->dateexpire)) {
351                     $json['expires'] = date('c', $this->_data->dateexpire);
352                 }
353                 unset($json['uid']);
354             }
355             // For Badge.
356             if ($type == OPEN_BADGES_V2_TYPE_BADGE) {
357                 $json['@context'] = OPEN_BADGES_V2_CONTEXT;
358                 $json['id'] = $classurl->out(false);
359                 $json['type'] = OPEN_BADGES_V2_TYPE_BADGE;
360                 $json['version'] = $this->_data->version;
361                 $json['criteria'] = $this->get_criteria_badge_class();
362                 $json['issuer'] = $this->get_issuer();
363                 $json['@language'] = $this->_data->language;
364                 if (!empty($relatedbadges = $this->get_related_badges($badge))) {
365                     $json['related'] = $relatedbadges;
366                 }
367                 if ($endorsement = $this->get_endorsement()) {
368                     $endorsementurl = new moodle_url('/badges/endorsement_json.php', array('id' => $this->_data->id));
369                     $json['endorsement'] = $endorsementurl->out(false);
370                 }
371                 if ($alignments = $this->get_alignments()) {
372                     $json['alignments'] = $alignments;
373                 }
374                 if ($this->_data->imageauthorname ||
375                         $this->_data->imageauthoremail ||
376                         $this->_data->imageauthorurl ||
377                         $this->_data->imagecaption) {
378                     $storage = get_file_storage();
379                     $imagefile = $storage->get_file($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f1.png');
380                     if ($imagefile) {
381                         $imagedata = base64_encode($imagefile->get_content());
382                     } else {
383                         // The file might not exist in unit tests.
384                         if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
385                             $imagedata = '';
386                         } else {
387                             throw new coding_exception('Image file does not exist.');
388                         }
389                     }
390                     $json['image'] = 'data:image/png;base64,' . $imagedata;
391                 }
392             }
394             // For issuer.
395             if ($type == OPEN_BADGES_V2_TYPE_ISSUER) {
396                 $json['@context'] = OPEN_BADGES_V2_CONTEXT;
397                 $json['id'] = $issuerurl->out(false);
398                 $json['type'] = OPEN_BADGES_V2_TYPE_ISSUER;
399             }
400         }
401     }