Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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/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              $this->embed_data_badge_version2($assertion, OPEN_BADGES_V2_TYPE_ASSERTION);
 165          }
 166          return $assertion;
 167      }
 168  
 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      }
 212  
 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      }
 240  
 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      }
 263  
 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      }
 275  
 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      }
 293  
 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      }
 319  
 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              }
 335  
 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              }
 393  
 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      }
 402  }