Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   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  
 200              $params = ['id' => $this->get_badge_id()];
 201              $badgeurl = new moodle_url('/badges/badgeclass.php', $params);
 202              $class['criteria'] = $badgeurl->out(false); // Currently badge URL.
 203              if ($issued) {
 204                  $params = ['id' => $this->get_badge_id(), 'obversion' => $this->_obversion];
 205                  $issuerurl = new moodle_url('/badges/issuer_json.php', $params);
 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      }
 215  
 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 ($this->_obversion == 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      }
 243  
 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      }
 266  
 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      }
 278  
 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          $params = ['id' => $this->get_badge_id()];
 288          $badgeurl = new moodle_url('/badges/badgeclass.php', $params);
 289          if (!empty($narrative)) {
 290              $criteria = [];
 291              $criteria['id'] = $badgeurl->out(false);
 292              $criteria['narrative'] = $narrative;
 293              return $criteria;
 294          } else {
 295              return $badgeurl->out(false);
 296          }
 297      }
 298  
 299      /**
 300       * Get alignment of the badge.
 301       *
 302       * @return array information.
 303       */
 304      public function get_alignments() {
 305          global $DB;
 306          $badgeid = $this->_data->id;
 307          $alignments = array();
 308          $items = $DB->get_records_select('badge_alignment', 'badgeid = ?', array($badgeid));
 309          foreach ($items as $item) {
 310              $alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
 311              if ($item->targetdescription) {
 312                  $alignment['targetDescription'] = $item->targetdescription;
 313              }
 314              if ($item->targetframework) {
 315                  $alignment['targetFramework'] = $item->targetframework;
 316              }
 317              if ($item->targetcode) {
 318                  $alignment['targetCode'] = $item->targetcode;
 319              }
 320              $alignments[] = $alignment;
 321          }
 322          return $alignments;
 323      }
 324  
 325      /**
 326       * Embed data of Open Badges Specification Version 2.0 to json.
 327       *
 328       * @param array $json for assertion, badges, issuer.
 329       * @param string $type Content type.
 330       */
 331      protected function embed_data_badge_version2 (&$json, $type = OPEN_BADGES_V2_TYPE_ASSERTION) {
 332          // Specification Version 2.0.
 333          if ($this->_obversion >= OPEN_BADGES_V2) {
 334              $badge = new badge($this->_data->id);
 335              if (empty($this->_data->courseid)) {
 336                  $context = context_system::instance();
 337              } else {
 338                  $context = context_course::instance($this->_data->courseid);
 339              }
 340  
 341              $hash = $this->_data->uniquehash;
 342              $assertionsurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
 343              $classurl = new moodle_url(
 344                  '/badges/badge_json.php',
 345                  array('id' => $this->get_badge_id())
 346              );
 347              $issuerurl = new moodle_url('/badges/issuer_json.php', ['id' => $this->get_badge_id()]);
 348              // For assertion.
 349              if ($type == OPEN_BADGES_V2_TYPE_ASSERTION) {
 350                  $json['@context'] = OPEN_BADGES_V2_CONTEXT;
 351                  $json['type'] = OPEN_BADGES_V2_TYPE_ASSERTION;
 352                  $json['id'] = $assertionsurl->out(false);
 353                  $json['badge'] = $this->get_badge_class();
 354                  $json['issuedOn'] = date('c', $this->_data->dateissued);
 355                  if (!empty($this->_data->dateexpire)) {
 356                      $json['expires'] = date('c', $this->_data->dateexpire);
 357                  }
 358                  unset($json['uid']);
 359              }
 360              // For Badge.
 361              if ($type == OPEN_BADGES_V2_TYPE_BADGE) {
 362                  $json['@context'] = OPEN_BADGES_V2_CONTEXT;
 363                  $json['id'] = $classurl->out(false);
 364                  $json['type'] = OPEN_BADGES_V2_TYPE_BADGE;
 365                  $json['version'] = $this->_data->version;
 366                  $json['criteria'] = $this->get_criteria_badge_class();
 367                  $json['issuer'] = $this->get_issuer();
 368                  $json['@language'] = $this->_data->language;
 369                  if (!empty($relatedbadges = $this->get_related_badges($badge))) {
 370                      $json['related'] = $relatedbadges;
 371                  }
 372                  if ($endorsement = $this->get_endorsement()) {
 373                      $endorsementurl = new moodle_url('/badges/endorsement_json.php', array('id' => $this->_data->id));
 374                      $json['endorsement'] = $endorsementurl->out(false);
 375                  }
 376                  if ($alignments = $this->get_alignments()) {
 377                      $json['alignments'] = $alignments;
 378                  }
 379                  if ($this->_data->imageauthorname ||
 380                          $this->_data->imageauthoremail ||
 381                          $this->_data->imageauthorurl ||
 382                          $this->_data->imagecaption) {
 383                      $storage = get_file_storage();
 384                      $imagefile = $storage->get_file($context->id, 'badges', 'badgeimage', $this->_data->id, '/', 'f3.png');
 385                      if ($imagefile) {
 386                          $imagedata = base64_encode($imagefile->get_content());
 387                      } else {
 388                          // The file might not exist in unit tests.
 389                          if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
 390                              $imagedata = '';
 391                          } else {
 392                              throw new coding_exception('Image file does not exist.');
 393                          }
 394                      }
 395                      $json['image'] = 'data:image/png;base64,' . $imagedata;
 396                  }
 397              }
 398  
 399              // For issuer.
 400              if ($type == OPEN_BADGES_V2_TYPE_ISSUER) {
 401                  $json['@context'] = OPEN_BADGES_V2_CONTEXT;
 402                  $json['id'] = $issuerurl->out(false);
 403                  $json['type'] = OPEN_BADGES_V2_TYPE_ISSUER;
 404              }
 405          }
 406      }
 407  }