Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 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   * Communicate with backpacks.
  19   *
  20   * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
  23   */
  24  
  25  namespace core_badges;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  require_once($CFG->libdir . '/filelib.php');
  30  
  31  use cache;
  32  use coding_exception;
  33  use core_badges\external\assertion_exporter;
  34  use core_badges\external\collection_exporter;
  35  use core_badges\external\issuer_exporter;
  36  use core_badges\external\badgeclass_exporter;
  37  use curl;
  38  use stdClass;
  39  use context_system;
  40  
  41  define('BADGE_ACCESS_TOKEN', 'access');
  42  define('BADGE_USER_ID_TOKEN', 'user_id');
  43  define('BADGE_BACKPACK_ID_TOKEN', 'backpack_id');
  44  define('BADGE_REFRESH_TOKEN', 'refresh');
  45  define('BADGE_EXPIRES_TOKEN', 'expires');
  46  
  47  /**
  48   * Class for communicating with backpacks.
  49   *
  50   * @package   core_badges
  51   * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
  52   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  53   */
  54  class backpack_api {
  55  
  56      /** @var string The email address of the issuer or the backpack owner. */
  57      private $email;
  58  
  59      /** @var string The base url used for api requests to this backpack. */
  60      private $backpackapiurl;
  61  
  62      /** @var integer The backpack api version to use. */
  63      private $backpackapiversion;
  64  
  65      /** @var string The password to authenticate requests. */
  66      private $password;
  67  
  68      /** @var boolean User or site api requests. */
  69      private $isuserbackpack;
  70  
  71      /** @var integer The id of the backpack we are talking to. */
  72      private $backpackid;
  73  
  74      /** @var \backpack_api_mapping[] List of apis for the user or site using api version 1 or 2. */
  75      private $mappings = [];
  76  
  77      /**
  78       * Create a wrapper to communicate with the backpack.
  79       *
  80       * The resulting class can only do either site backpack communication or
  81       * user backpack communication.
  82       *
  83       * @param stdClass $sitebackpack The site backpack record
  84       * @param mixed $userbackpack Optional - if passed it represents the users backpack.
  85       */
  86      public function __construct($sitebackpack, $userbackpack = false) {
  87          global $CFG;
  88          $admin = get_admin();
  89  
  90          $this->backpackapiurl = $sitebackpack->backpackapiurl;
  91          $this->backpackapiversion = $sitebackpack->apiversion;
  92          $this->password = $sitebackpack->password;
  93          $this->email = $sitebackpack->backpackemail;
  94          $this->isuserbackpack = false;
  95          $this->backpackid = $sitebackpack->id;
  96          if (!empty($userbackpack)) {
  97              $this->isuserbackpack = true;
  98              $this->password = $userbackpack->password;
  99              $this->email = $userbackpack->email;
 100          }
 101  
 102          $this->define_mappings();
 103          // Clear the last authentication error.
 104          backpack_api_mapping::set_authentication_error('');
 105      }
 106  
 107      /**
 108       * Define the mappings supported by this usage and api version.
 109       */
 110      private function define_mappings() {
 111          if ($this->backpackapiversion == OPEN_BADGES_V2) {
 112              if ($this->isuserbackpack) {
 113                  $mapping = [];
 114                  $mapping[] = [
 115                      'collections',                              // Action.
 116                      '[URL]/backpack/collections',               // URL
 117                      [],                                         // Post params.
 118                      '',                                         // Request exporter.
 119                      'core_badges\external\collection_exporter', // Response exporter.
 120                      true,                                       // Multiple.
 121                      'get',                                      // Method.
 122                      true,                                       // JSON Encoded.
 123                      true                                        // Auth required.
 124                  ];
 125                  $mapping[] = [
 126                      'user',                                     // Action.
 127                      '[SCHEME]://[HOST]/o/token',                // URL
 128                      ['username' => '[EMAIL]', 'password' => '[PASSWORD]'], // Post params.
 129                      '',                                         // Request exporter.
 130                      'oauth_token_response',                     // Response exporter.
 131                      false,                                      // Multiple.
 132                      'post',                                     // Method.
 133                      false,                                      // JSON Encoded.
 134                      false,                                      // Auth required.
 135                  ];
 136                  $mapping[] = [
 137                      'assertion',                                // Action.
 138                      // Badgr.io does not return the public information about a badge
 139                      // if the issuer is associated with another user. We need to pass
 140                      // the expand parameters which are not in any specification to get
 141                      // additional information about the assertion in a single request.
 142                      '[URL]/backpack/assertions/[PARAM2]?expand=badgeclass&expand=issuer',
 143                      [],                                         // Post params.
 144                      '',                                         // Request exporter.
 145                      'core_badges\external\assertion_exporter',  // Response exporter.
 146                      false,                                      // Multiple.
 147                      'get',                                      // Method.
 148                      true,                                       // JSON Encoded.
 149                      true                                        // Auth required.
 150                  ];
 151                  $mapping[] = [
 152                      'importbadge',                                // Action.
 153                      // Badgr.io does not return the public information about a badge
 154                      // if the issuer is associated with another user. We need to pass
 155                      // the expand parameters which are not in any specification to get
 156                      // additional information about the assertion in a single request.
 157                      '[URL]/backpack/import',
 158                      ['url' => '[PARAM]'],  // Post params.
 159                      '',                                             // Request exporter.
 160                      'core_badges\external\assertion_exporter',      // Response exporter.
 161                      false,                                          // Multiple.
 162                      'post',                                         // Method.
 163                      true,                                           // JSON Encoded.
 164                      true                                            // Auth required.
 165                  ];
 166                  $mapping[] = [
 167                      'badges',                                   // Action.
 168                      '[URL]/backpack/collections/[PARAM1]',      // URL
 169                      [],                                         // Post params.
 170                      '',                                         // Request exporter.
 171                      'core_badges\external\collection_exporter', // Response exporter.
 172                      true,                                       // Multiple.
 173                      'get',                                      // Method.
 174                      true,                                       // JSON Encoded.
 175                      true                                        // Auth required.
 176                  ];
 177                  foreach ($mapping as $map) {
 178                      $map[] = true; // User api function.
 179                      $map[] = OPEN_BADGES_V2; // V2 function.
 180                      $this->mappings[] = new backpack_api_mapping(...$map);
 181                  }
 182              } else {
 183                  $mapping = [];
 184                  $mapping[] = [
 185                      'user',                                     // Action.
 186                      '[SCHEME]://[HOST]/o/token',                // URL
 187                      ['username' => '[EMAIL]', 'password' => '[PASSWORD]'], // Post params.
 188                      '',                                         // Request exporter.
 189                      'oauth_token_response',                     // Response exporter.
 190                      false,                                      // Multiple.
 191                      'post',                                     // Method.
 192                      false,                                      // JSON Encoded.
 193                      false                                       // Auth required.
 194                  ];
 195                  $mapping[] = [
 196                      'issuers',                                  // Action.
 197                      '[URL]/issuers',                            // URL
 198                      '[PARAM]',                                  // Post params.
 199                      'core_badges\external\issuer_exporter',     // Request exporter.
 200                      'core_badges\external\issuer_exporter',     // Response exporter.
 201                      false,                                      // Multiple.
 202                      'post',                                     // Method.
 203                      true,                                       // JSON Encoded.
 204                      true                                        // Auth required.
 205                  ];
 206                  $mapping[] = [
 207                      'badgeclasses',                             // Action.
 208                      '[URL]/issuers/[PARAM2]/badgeclasses',      // URL
 209                      '[PARAM]',                                  // Post params.
 210                      'core_badges\external\badgeclass_exporter', // Request exporter.
 211                      'core_badges\external\badgeclass_exporter', // Response exporter.
 212                      false,                                      // Multiple.
 213                      'post',                                     // Method.
 214                      true,                                       // JSON Encoded.
 215                      true                                        // Auth required.
 216                  ];
 217                  $mapping[] = [
 218                      'assertions',                               // Action.
 219                      '[URL]/badgeclasses/[PARAM2]/assertions',   // URL
 220                      '[PARAM]',                                  // Post params.
 221                      'core_badges\external\assertion_exporter', // Request exporter.
 222                      'core_badges\external\assertion_exporter', // Response exporter.
 223                      false,                                      // Multiple.
 224                      'post',                                     // Method.
 225                      true,                                       // JSON Encoded.
 226                      true                                        // Auth required.
 227                  ];
 228                  $mapping[] = [
 229                      'updateassertion',                                // Action.
 230                      '[URL]/assertions/[PARAM2]?expand=badgeclass&expand=issuer',
 231                      '[PARAM]',                                  // Post params.
 232                      'core_badges\external\assertion_exporter', // Request exporter.
 233                      'core_badges\external\assertion_exporter', // Response exporter.
 234                      false,                                      // Multiple.
 235                      'put',                                     // Method.
 236                      true,                                       // JSON Encoded.
 237                      true                                        // Auth required.
 238                  ];
 239                  foreach ($mapping as $map) {
 240                      $map[] = false; // Site api function.
 241                      $map[] = OPEN_BADGES_V2; // V2 function.
 242                      $this->mappings[] = new backpack_api_mapping(...$map);
 243                  }
 244              }
 245          } else {
 246              if ($this->isuserbackpack) {
 247                  $mapping = [];
 248                  $mapping[] = [
 249                      'user',                                     // Action.
 250                      '[URL]/displayer/convert/email',            // URL
 251                      ['email' => '[EMAIL]'],                     // Post params.
 252                      '',                                         // Request exporter.
 253                      'convert_email_response',                   // Response exporter.
 254                      false,                                      // Multiple.
 255                      'post',                                     // Method.
 256                      false,                                      // JSON Encoded.
 257                      false                                       // Auth required.
 258                  ];
 259                  $mapping[] = [
 260                      'groups',                                   // Action.
 261                      '[URL]/displayer/[PARAM1]/groups.json',     // URL
 262                      [],                                         // Post params.
 263                      '',                                         // Request exporter.
 264                      '',                                         // Response exporter.
 265                      false,                                      // Multiple.
 266                      'get',                                      // Method.
 267                      true,                                       // JSON Encoded.
 268                      true                                        // Auth required.
 269                  ];
 270                  $mapping[] = [
 271                      'badges',                                   // Action.
 272                      '[URL]/displayer/[PARAM2]/group/[PARAM1].json',     // URL
 273                      [],                                         // Post params.
 274                      '',                                         // Request exporter.
 275                      '',                                         // Response exporter.
 276                      false,                                      // Multiple.
 277                      'get',                                      // Method.
 278                      true,                                       // JSON Encoded.
 279                      true                                        // Auth required.
 280                  ];
 281                  foreach ($mapping as $map) {
 282                      $map[] = true; // User api function.
 283                      $map[] = OPEN_BADGES_V1; // V1 function.
 284                      $this->mappings[] = new backpack_api_mapping(...$map);
 285                  }
 286              } else {
 287                  $mapping = [];
 288                  $mapping[] = [
 289                      'user',                                     // Action.
 290                      '[URL]/displayer/convert/email',            // URL
 291                      ['email' => '[EMAIL]'],                     // Post params.
 292                      '',                                         // Request exporter.
 293                      'convert_email_response',                   // Response exporter.
 294                      false,                                      // Multiple.
 295                      'post',                                     // Method.
 296                      false,                                      // JSON Encoded.
 297                      false                                       // Auth required.
 298                  ];
 299                  foreach ($mapping as $map) {
 300                      $map[] = false; // Site api function.
 301                      $map[] = OPEN_BADGES_V1; // V1 function.
 302                      $this->mappings[] = new backpack_api_mapping(...$map);
 303                  }
 304              }
 305          }
 306      }
 307  
 308      /**
 309       * Make an api request
 310       *
 311       * @param string $action The api function.
 312       * @param string $collection An api parameter
 313       * @param string $entityid An api parameter
 314       * @param string $postdata The body of the api request.
 315       * @return mixed
 316       */
 317      private function curl_request($action, $collection = null, $entityid = null, $postdata = null) {
 318          global $CFG, $SESSION;
 319  
 320          $curl = new curl();
 321          $authrequired = false;
 322          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 323              $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
 324              if (isset($SESSION->$useridkey)) {
 325                  if ($collection == null) {
 326                      $collection = $SESSION->$useridkey;
 327                  } else {
 328                      $entityid = $SESSION->$useridkey;
 329                  }
 330              }
 331          }
 332          foreach ($this->mappings as $mapping) {
 333              if ($mapping->is_match($action)) {
 334                  return $mapping->request(
 335                      $this->backpackapiurl,
 336                      $collection,
 337                      $entityid,
 338                      $this->email,
 339                      $this->password,
 340                      $postdata,
 341                      $this->backpackid
 342                  );
 343              }
 344          }
 345  
 346          throw new coding_exception('Unknown request');
 347      }
 348  
 349      /**
 350       * Get the id to use for requests with this api.
 351       *
 352       * @return integer
 353       */
 354      private function get_auth_user_id() {
 355          global $USER;
 356  
 357          if ($this->isuserbackpack) {
 358              return $USER->id;
 359          } else {
 360              // The access tokens for the system backpack are shared.
 361              return -1;
 362          }
 363      }
 364  
 365      /**
 366       * Get the name of the key to store this access token type.
 367       *
 368       * @param string $type
 369       * @return string
 370       */
 371      private function get_token_key($type) {
 372          // This should be removed when everything has a mapping.
 373          $prefix = 'badges_';
 374          if ($this->isuserbackpack) {
 375              $prefix .= 'user_backpack_';
 376          } else {
 377              $prefix .= 'site_backpack_';
 378          }
 379          $prefix .= $type . '_token';
 380          return $prefix;
 381      }
 382  
 383      /**
 384       * Normalise the return from a missing user request.
 385       *
 386       * @param string $status
 387       * @return mixed
 388       */
 389      private function check_status($status) {
 390          // V1 ONLY.
 391          switch($status) {
 392              case "missing":
 393                  $response = array(
 394                      'status'  => $status,
 395                      'message' => get_string('error:nosuchuser', 'badges')
 396                  );
 397                  return $response;
 398          }
 399          return false;
 400      }
 401  
 402      /**
 403       * Make an api request to get an assertion
 404       *
 405       * @param string $entityid The id of the assertion.
 406       * @return mixed
 407       */
 408      public function get_assertion($entityid) {
 409          // V2 Only.
 410          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 411              throw new coding_exception('Not supported in this backpack API');
 412          }
 413  
 414          return $this->curl_request('assertion', null, $entityid);
 415      }
 416  
 417      /**
 418       * Create a badgeclass assertion.
 419       *
 420       * @param string $entityid The id of the badge class.
 421       * @param string $data The structure of the badge class assertion.
 422       * @return mixed
 423       */
 424      public function put_badgeclass_assertion($entityid, $data) {
 425          // V2 Only.
 426          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 427              throw new coding_exception('Not supported in this backpack API');
 428          }
 429  
 430          return $this->curl_request('assertions', null, $entityid, $data);
 431      }
 432  
 433      /**
 434       * Update a badgeclass assertion.
 435       *
 436       * @param string $entityid The id of the badge class.
 437       * @param array $data The structure of the badge class assertion.
 438       * @return mixed
 439       */
 440      public function update_assertion(string $entityid, array $data) {
 441          // V2 Only.
 442          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 443              throw new coding_exception('Not supported in this backpack API');
 444          }
 445  
 446          return $this->curl_request('updateassertion', null, $entityid, $data);
 447      }
 448  
 449      /**
 450       * Import a badge assertion into a backpack. This is used to handle cross domain backpacks.
 451       *
 452       * @param string $data The structure of the badge class assertion.
 453       * @return mixed
 454       * @throws coding_exception
 455       */
 456      public function import_badge_assertion(string $data) {
 457          // V2 Only.
 458          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 459              throw new coding_exception('Not supported in this backpack API');
 460          }
 461  
 462          return $this->curl_request('importbadge', null, null, $data);
 463      }
 464  
 465      /**
 466       * Select collections from a backpack.
 467       *
 468       * @param string $backpackid The id of the backpack
 469       * @param stdClass[] $collections List of collections with collectionid or entityid.
 470       * @return boolean
 471       */
 472      public function set_backpack_collections($backpackid, $collections) {
 473          global $DB, $USER;
 474  
 475          // Delete any previously selected collections.
 476          $sqlparams = array('backpack' => $backpackid);
 477          $select = 'backpackid = :backpack ';
 478          $DB->delete_records_select('badge_external', $select, $sqlparams);
 479          $badgescache = cache::make('core', 'externalbadges');
 480  
 481          // Insert selected collections if they are not in database yet.
 482          foreach ($collections as $collection) {
 483              $obj = new stdClass();
 484              $obj->backpackid = $backpackid;
 485              if ($this->backpackapiversion == OPEN_BADGES_V1) {
 486                  $obj->collectionid = (int) $collection;
 487              } else {
 488                  $obj->entityid = $collection;
 489                  $obj->collectionid = -1;
 490              }
 491              if (!$DB->record_exists('badge_external', (array) $obj)) {
 492                  $DB->insert_record('badge_external', $obj);
 493              }
 494          }
 495          $badgescache->delete($USER->id);
 496          return true;
 497      }
 498  
 499      /**
 500       * Create a badgeclass
 501       *
 502       * @param string $entityid The id of the entity.
 503       * @param string $data The structure of the badge class.
 504       * @return mixed
 505       */
 506      public function put_badgeclass($entityid, $data) {
 507          // V2 Only.
 508          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 509              throw new coding_exception('Not supported in this backpack API');
 510          }
 511  
 512          return $this->curl_request('badgeclasses', null, $entityid, $data);
 513      }
 514  
 515      /**
 516       * Create an issuer
 517       *
 518       * @param string $data The structure of the issuer.
 519       * @return mixed
 520       */
 521      public function put_issuer($data) {
 522          // V2 Only.
 523          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 524              throw new coding_exception('Not supported in this backpack API');
 525          }
 526  
 527          return $this->curl_request('issuers', null, null, $data);
 528      }
 529  
 530      /**
 531       * Delete any user access tokens in the session so we will attempt to get new ones.
 532       *
 533       * @return void
 534       */
 535      public function clear_system_user_session() {
 536          global $SESSION;
 537  
 538          $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
 539          unset($SESSION->$useridkey);
 540  
 541          $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
 542          unset($SESSION->$expireskey);
 543      }
 544  
 545      /**
 546       * Authenticate using the stored email and password and save the valid access tokens.
 547       *
 548       * @return mixed The id of the authenticated user as returned by the backpack. Can have
 549       *    different formats - numeric, empty, object with 'error' property, etc.
 550       */
 551      public function authenticate() {
 552          global $SESSION;
 553  
 554          $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
 555          $backpackid = isset($SESSION->$backpackidkey) ? $SESSION->$backpackidkey : 0;
 556          // If the backpack is changed we need to expire sessions.
 557          if ($backpackid == $this->backpackid) {
 558              if ($this->backpackapiversion == OPEN_BADGES_V2) {
 559                  $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
 560                  $authuserid = isset($SESSION->$useridkey) ? $SESSION->$useridkey : 0;
 561                  if ($authuserid == $this->get_auth_user_id()) {
 562                      $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
 563                      if (isset($SESSION->$expireskey)) {
 564                          $expires = $SESSION->$expireskey;
 565                          if ($expires > time()) {
 566                              // We have a current access token for this user
 567                              // that has not expired.
 568                              return -1;
 569                          }
 570                      }
 571                  }
 572              } else {
 573                  $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
 574                  $authuserid = isset($SESSION->$useridkey) ? $SESSION->$useridkey : 0;
 575                  if (!empty($authuserid)) {
 576                      return $authuserid;
 577                  }
 578              }
 579          }
 580          return $this->curl_request('user', $this->email);
 581      }
 582  
 583      /**
 584       * Get all collections in this backpack.
 585       *
 586       * @return stdClass[] The collections.
 587       */
 588      public function get_collections() {
 589          global $PAGE;
 590  
 591          if ($this->authenticate()) {
 592              if ($this->backpackapiversion == OPEN_BADGES_V1) {
 593                  $result = $this->curl_request('groups');
 594                  if (isset($result->groups)) {
 595                      $result = $result->groups;
 596                  }
 597              } else {
 598                  $result = $this->curl_request('collections');
 599              }
 600              if ($result) {
 601                  return $result;
 602              }
 603          }
 604          return [];
 605      }
 606  
 607      /**
 608       * Get one collection by id.
 609       *
 610       * @param integer $collectionid
 611       * @return stdClass The collection.
 612       */
 613      public function get_collection_record($collectionid) {
 614          global $DB;
 615  
 616          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 617              return $DB->get_fieldset_select('badge_external', 'collectionid', 'backpackid = :bid', array('bid' => $collectionid));
 618          } else {
 619              return $DB->get_fieldset_select('badge_external', 'entityid', 'backpackid = :bid', array('bid' => $collectionid));
 620          }
 621      }
 622  
 623      /**
 624       * Disconnect the backpack from this user.
 625       *
 626       * @param integer $userid The user in Moodle
 627       * @param integer $backpackid The backpack to disconnect
 628       * @return boolean
 629       */
 630      public function disconnect_backpack($userid, $backpackid) {
 631          global $DB, $USER;
 632  
 633          if (\core\session\manager::is_loggedinas() || $userid != $USER->id) {
 634              // Can't change someone elses backpack settings.
 635              return false;
 636          }
 637  
 638          $badgescache = cache::make('core', 'externalbadges');
 639  
 640          $DB->delete_records('badge_external', array('backpackid' => $backpackid));
 641          $DB->delete_records('badge_backpack', array('userid' => $userid));
 642          $badgescache->delete($userid);
 643          $this->clear_system_user_session();
 644  
 645          return true;
 646      }
 647  
 648      /**
 649       * Handle the response from getting a collection to map to an id.
 650       *
 651       * @param stdClass $data The response data.
 652       * @return string The collection id.
 653       */
 654      public function get_collection_id_from_response($data) {
 655          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 656              return $data->groupId;
 657          } else {
 658              return $data->entityId;
 659          }
 660      }
 661  
 662      /**
 663       * Get the last error message returned during an authentication request.
 664       *
 665       * @return string
 666       */
 667      public function get_authentication_error() {
 668          return backpack_api_mapping::get_authentication_error();
 669      }
 670  
 671      /**
 672       * Get the list of badges in a collection.
 673       *
 674       * @param stdClass $collection The collection to deal with.
 675       * @param boolean $expanded Fetch all the sub entities.
 676       * @return stdClass[]
 677       */
 678      public function get_badges($collection, $expanded = false) {
 679          global $PAGE;
 680  
 681          if ($this->authenticate()) {
 682              if ($this->backpackapiversion == OPEN_BADGES_V1) {
 683                  if (empty($collection->collectionid)) {
 684                      return [];
 685                  }
 686                  $result = $this->curl_request('badges', $collection->collectionid);
 687                  return $result->badges;
 688              } else {
 689                  if (empty($collection->entityid)) {
 690                      return [];
 691                  }
 692                  // Now we can make requests.
 693                  $badges = $this->curl_request('badges', $collection->entityid);
 694                  if (count($badges) == 0) {
 695                      return [];
 696                  }
 697                  $badges = $badges[0];
 698                  if ($expanded) {
 699                      $publicassertions = [];
 700                      $context = context_system::instance();
 701                      $output = $PAGE->get_renderer('core', 'badges');
 702                      foreach ($badges->assertions as $assertion) {
 703                          $remoteassertion = $this->get_assertion($assertion);
 704                          // Remote badge was fetched nested in the assertion.
 705                          $remotebadge = $remoteassertion->badgeclass;
 706                          if (!$remotebadge) {
 707                              continue;
 708                          }
 709                          $apidata = badgeclass_exporter::map_external_data($remotebadge, $this->backpackapiversion);
 710                          $exporterinstance = new badgeclass_exporter($apidata, ['context' => $context]);
 711                          $remotebadge = $exporterinstance->export($output);
 712  
 713                          $remoteissuer = $remotebadge->issuer;
 714                          $apidata = issuer_exporter::map_external_data($remoteissuer, $this->backpackapiversion);
 715                          $exporterinstance = new issuer_exporter($apidata, ['context' => $context]);
 716                          $remoteissuer = $exporterinstance->export($output);
 717  
 718                          $badgeclone = clone $remotebadge;
 719                          $badgeclone->issuer = $remoteissuer;
 720                          $remoteassertion->badge = $badgeclone;
 721                          $remotebadge->assertion = $remoteassertion;
 722                          $publicassertions[] = $remotebadge;
 723                      }
 724                      $badges = $publicassertions;
 725                  }
 726                  return $badges;
 727              }
 728          }
 729      }
 730  }