Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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->backpackapiurl = $sitebackpack->backpackapiurl;
  92          $this->backpackapiversion = $sitebackpack->apiversion;
  93          $this->password = $sitebackpack->password;
  94          $this->email = !empty($CFG->badges_defaultissuercontact) ? $CFG->badges_defaultissuercontact : '';
  95          $this->isuserbackpack = false;
  96          $this->backpackid = $sitebackpack->id;
  97          if (!empty($userbackpack)) {
  98              if ($userbackpack->externalbackpackid != $sitebackpack->id) {
  99                  throw new coding_exception('Incorrect backpack');
 100              }
 101              $this->isuserbackpack = true;
 102              $this->password = $userbackpack->password;
 103              $this->email = $userbackpack->email;
 104          }
 105  
 106          $this->define_mappings();
 107          // Clear the last authentication error.
 108          backpack_api_mapping::set_authentication_error('');
 109      }
 110  
 111      /**
 112       * Define the mappings supported by this usage and api version.
 113       */
 114      private function define_mappings() {
 115          if ($this->backpackapiversion == OPEN_BADGES_V2) {
 116              if ($this->isuserbackpack) {
 117                  $mapping = [];
 118                  $mapping[] = [
 119                      'collections',                              // Action.
 120                      '[URL]/backpack/collections',               // URL
 121                      [],                                         // Post params.
 122                      '',                                         // Request exporter.
 123                      'core_badges\external\collection_exporter', // Response exporter.
 124                      true,                                       // Multiple.
 125                      'get',                                      // Method.
 126                      true,                                       // JSON Encoded.
 127                      true                                        // Auth required.
 128                  ];
 129                  $mapping[] = [
 130                      'user',                                     // Action.
 131                      '[SCHEME]://[HOST]/o/token',                // URL
 132                      ['username' => '[EMAIL]', 'password' => '[PASSWORD]'], // Post params.
 133                      '',                                         // Request exporter.
 134                      'oauth_token_response',                     // Response exporter.
 135                      false,                                      // Multiple.
 136                      'post',                                     // Method.
 137                      false,                                      // JSON Encoded.
 138                      false,                                      // Auth required.
 139                  ];
 140                  $mapping[] = [
 141                      'assertion',                                // Action.
 142                      // Badgr.io does not return the public information about a badge
 143                      // if the issuer is associated with another user. We need to pass
 144                      // the expand parameters which are not in any specification to get
 145                      // additional information about the assertion in a single request.
 146                      '[URL]/backpack/assertions/[PARAM2]?expand=badgeclass&expand=issuer',
 147                      [],                                         // Post params.
 148                      '',                                         // Request exporter.
 149                      'core_badges\external\assertion_exporter',  // Response exporter.
 150                      false,                                      // Multiple.
 151                      'get',                                      // Method.
 152                      true,                                       // JSON Encoded.
 153                      true                                        // Auth required.
 154                  ];
 155                  $mapping[] = [
 156                      'badges',                                   // Action.
 157                      '[URL]/backpack/collections/[PARAM1]',      // URL
 158                      [],                                         // Post params.
 159                      '',                                         // Request exporter.
 160                      'core_badges\external\collection_exporter', // Response exporter.
 161                      true,                                       // Multiple.
 162                      'get',                                      // Method.
 163                      true,                                       // JSON Encoded.
 164                      true                                        // Auth required.
 165                  ];
 166                  foreach ($mapping as $map) {
 167                      $map[] = true; // User api function.
 168                      $map[] = OPEN_BADGES_V2; // V2 function.
 169                      $this->mappings[] = new backpack_api_mapping(...$map);
 170                  }
 171              } else {
 172                  $mapping = [];
 173                  $mapping[] = [
 174                      'user',                                     // Action.
 175                      '[SCHEME]://[HOST]/o/token',                // URL
 176                      ['username' => '[EMAIL]', 'password' => '[PASSWORD]'], // Post params.
 177                      '',                                         // Request exporter.
 178                      'oauth_token_response',                     // Response exporter.
 179                      false,                                      // Multiple.
 180                      'post',                                     // Method.
 181                      false,                                      // JSON Encoded.
 182                      false                                       // Auth required.
 183                  ];
 184                  $mapping[] = [
 185                      'issuers',                                  // Action.
 186                      '[URL]/issuers',                            // URL
 187                      '[PARAM]',                                  // Post params.
 188                      'core_badges\external\issuer_exporter',     // Request exporter.
 189                      'core_badges\external\issuer_exporter',     // Response exporter.
 190                      false,                                      // Multiple.
 191                      'post',                                     // Method.
 192                      true,                                       // JSON Encoded.
 193                      true                                        // Auth required.
 194                  ];
 195                  $mapping[] = [
 196                      'badgeclasses',                             // Action.
 197                      '[URL]/issuers/[PARAM2]/badgeclasses',      // URL
 198                      '[PARAM]',                                  // Post params.
 199                      'core_badges\external\badgeclass_exporter', // Request exporter.
 200                      'core_badges\external\badgeclass_exporter', // Response exporter.
 201                      false,                                      // Multiple.
 202                      'post',                                     // Method.
 203                      true,                                       // JSON Encoded.
 204                      true                                        // Auth required.
 205                  ];
 206                  $mapping[] = [
 207                      'assertions',                               // Action.
 208                      '[URL]/badgeclasses/[PARAM2]/assertions',   // URL
 209                      '[PARAM]',                                  // Post params.
 210                      'core_badges\external\assertion_exporter', // Request exporter.
 211                      'core_badges\external\assertion_exporter', // Response exporter.
 212                      false,                                      // Multiple.
 213                      'post',                                     // Method.
 214                      true,                                       // JSON Encoded.
 215                      true                                        // Auth required.
 216                  ];
 217                  foreach ($mapping as $map) {
 218                      $map[] = false; // Site api function.
 219                      $map[] = OPEN_BADGES_V2; // V2 function.
 220                      $this->mappings[] = new backpack_api_mapping(...$map);
 221                  }
 222              }
 223          } else {
 224              if ($this->isuserbackpack) {
 225                  $mapping = [];
 226                  $mapping[] = [
 227                      'user',                                     // Action.
 228                      '[URL]/displayer/convert/email',            // URL
 229                      ['email' => '[EMAIL]'],                     // Post params.
 230                      '',                                         // Request exporter.
 231                      'convert_email_response',                   // Response exporter.
 232                      false,                                      // Multiple.
 233                      'post',                                     // Method.
 234                      false,                                      // JSON Encoded.
 235                      false                                       // Auth required.
 236                  ];
 237                  $mapping[] = [
 238                      'groups',                                   // Action.
 239                      '[URL]/displayer/[PARAM1]/groups.json',     // URL
 240                      [],                                         // Post params.
 241                      '',                                         // Request exporter.
 242                      '',                                         // Response exporter.
 243                      false,                                      // Multiple.
 244                      'get',                                      // Method.
 245                      true,                                       // JSON Encoded.
 246                      true                                        // Auth required.
 247                  ];
 248                  $mapping[] = [
 249                      'badges',                                   // Action.
 250                      '[URL]/displayer/[PARAM2]/group/[PARAM1].json',     // URL
 251                      [],                                         // Post params.
 252                      '',                                         // Request exporter.
 253                      '',                                         // Response exporter.
 254                      false,                                      // Multiple.
 255                      'get',                                      // Method.
 256                      true,                                       // JSON Encoded.
 257                      true                                        // Auth required.
 258                  ];
 259                  foreach ($mapping as $map) {
 260                      $map[] = true; // User api function.
 261                      $map[] = OPEN_BADGES_V1; // V1 function.
 262                      $this->mappings[] = new backpack_api_mapping(...$map);
 263                  }
 264              } else {
 265                  $mapping = [];
 266                  $mapping[] = [
 267                      'user',                                     // Action.
 268                      '[URL]/displayer/convert/email',            // URL
 269                      ['email' => '[EMAIL]'],                     // Post params.
 270                      '',                                         // Request exporter.
 271                      'convert_email_response',                   // Response exporter.
 272                      false,                                      // Multiple.
 273                      'post',                                     // Method.
 274                      false,                                      // JSON Encoded.
 275                      false                                       // Auth required.
 276                  ];
 277                  foreach ($mapping as $map) {
 278                      $map[] = false; // Site api function.
 279                      $map[] = OPEN_BADGES_V1; // V1 function.
 280                      $this->mappings[] = new backpack_api_mapping(...$map);
 281                  }
 282              }
 283          }
 284      }
 285  
 286      /**
 287       * Make an api request
 288       *
 289       * @param string $action The api function.
 290       * @param string $collection An api parameter
 291       * @param string $entityid An api parameter
 292       * @param string $postdata The body of the api request.
 293       * @return mixed
 294       */
 295      private function curl_request($action, $collection = null, $entityid = null, $postdata = null) {
 296          global $CFG, $SESSION;
 297  
 298          $curl = new curl();
 299          $authrequired = false;
 300          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 301              $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
 302              if (isset($SESSION->$useridkey)) {
 303                  if ($collection == null) {
 304                      $collection = $SESSION->$useridkey;
 305                  } else {
 306                      $entityid = $SESSION->$useridkey;
 307                  }
 308              }
 309          }
 310          foreach ($this->mappings as $mapping) {
 311              if ($mapping->is_match($action)) {
 312                  return $mapping->request(
 313                      $this->backpackapiurl,
 314                      $collection,
 315                      $entityid,
 316                      $this->email,
 317                      $this->password,
 318                      $postdata,
 319                      $this->backpackid
 320                  );
 321              }
 322          }
 323  
 324          throw new coding_exception('Unknown request');
 325      }
 326  
 327      /**
 328       * Get the id to use for requests with this api.
 329       *
 330       * @return integer
 331       */
 332      private function get_auth_user_id() {
 333          global $USER;
 334  
 335          if ($this->isuserbackpack) {
 336              return $USER->id;
 337          } else {
 338              // The access tokens for the system backpack are shared.
 339              return -1;
 340          }
 341      }
 342  
 343      /**
 344       * Get the name of the key to store this access token type.
 345       *
 346       * @param string $type
 347       * @return string
 348       */
 349      private function get_token_key($type) {
 350          // This should be removed when everything has a mapping.
 351          $prefix = 'badges_';
 352          if ($this->isuserbackpack) {
 353              $prefix .= 'user_backpack_';
 354          } else {
 355              $prefix .= 'site_backpack_';
 356          }
 357          $prefix .= $type . '_token';
 358          return $prefix;
 359      }
 360  
 361      /**
 362       * Normalise the return from a missing user request.
 363       *
 364       * @param string $status
 365       * @return mixed
 366       */
 367      private function check_status($status) {
 368          // V1 ONLY.
 369          switch($status) {
 370              case "missing":
 371                  $response = array(
 372                      'status'  => $status,
 373                      'message' => get_string('error:nosuchuser', 'badges')
 374                  );
 375                  return $response;
 376          }
 377          return false;
 378      }
 379  
 380      /**
 381       * Make an api request to get an assertion
 382       *
 383       * @param string $entityid The id of the assertion.
 384       * @return mixed
 385       */
 386      public function get_assertion($entityid) {
 387          // V2 Only.
 388          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 389              throw new coding_exception('Not supported in this backpack API');
 390          }
 391  
 392          return $this->curl_request('assertion', null, $entityid);
 393      }
 394  
 395      /**
 396       * Create a badgeclass assertion.
 397       *
 398       * @param string $entityid The id of the badge class.
 399       * @param string $data The structure of the badge class assertion.
 400       * @return mixed
 401       */
 402      public function put_badgeclass_assertion($entityid, $data) {
 403          // V2 Only.
 404          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 405              throw new coding_exception('Not supported in this backpack API');
 406          }
 407  
 408          return $this->curl_request('assertions', null, $entityid, $data);
 409      }
 410  
 411      /**
 412       * Select collections from a backpack.
 413       *
 414       * @param string $backpackid The id of the backpack
 415       * @param stdClass[] $collections List of collections with collectionid or entityid.
 416       * @return boolean
 417       */
 418      public function set_backpack_collections($backpackid, $collections) {
 419          global $DB, $USER;
 420  
 421          // Delete any previously selected collections.
 422          $sqlparams = array('backpack' => $backpackid);
 423          $select = 'backpackid = :backpack ';
 424          $DB->delete_records_select('badge_external', $select, $sqlparams);
 425          $badgescache = cache::make('core', 'externalbadges');
 426  
 427          // Insert selected collections if they are not in database yet.
 428          foreach ($collections as $collection) {
 429              $obj = new stdClass();
 430              $obj->backpackid = $backpackid;
 431              if ($this->backpackapiversion == OPEN_BADGES_V1) {
 432                  $obj->collectionid = (int) $collection;
 433              } else {
 434                  $obj->entityid = $collection;
 435                  $obj->collectionid = -1;
 436              }
 437              if (!$DB->record_exists('badge_external', (array) $obj)) {
 438                  $DB->insert_record('badge_external', $obj);
 439              }
 440          }
 441          $badgescache->delete($USER->id);
 442          return true;
 443      }
 444  
 445      /**
 446       * Create a badgeclass
 447       *
 448       * @param string $entityid The id of the entity.
 449       * @param string $data The structure of the badge class.
 450       * @return mixed
 451       */
 452      public function put_badgeclass($entityid, $data) {
 453          // V2 Only.
 454          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 455              throw new coding_exception('Not supported in this backpack API');
 456          }
 457  
 458          return $this->curl_request('badgeclasses', null, $entityid, $data);
 459      }
 460  
 461      /**
 462       * Create an issuer
 463       *
 464       * @param string $data The structure of the issuer.
 465       * @return mixed
 466       */
 467      public function put_issuer($data) {
 468          // V2 Only.
 469          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 470              throw new coding_exception('Not supported in this backpack API');
 471          }
 472  
 473          return $this->curl_request('issuers', null, null, $data);
 474      }
 475  
 476      /**
 477       * Delete any user access tokens in the session so we will attempt to get new ones.
 478       *
 479       * @return void
 480       */
 481      public function clear_system_user_session() {
 482          global $SESSION;
 483  
 484          $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
 485          unset($SESSION->$useridkey);
 486  
 487          $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
 488          unset($SESSION->$expireskey);
 489      }
 490  
 491      /**
 492       * Authenticate using the stored email and password and save the valid access tokens.
 493       *
 494       * @return integer The id of the authenticated user.
 495       */
 496      public function authenticate() {
 497          global $SESSION;
 498  
 499          $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
 500          $backpackid = isset($SESSION->$backpackidkey) ? $SESSION->$backpackidkey : 0;
 501          // If the backpack is changed we need to expire sessions.
 502          if ($backpackid == $this->backpackid) {
 503              if ($this->backpackapiversion == OPEN_BADGES_V2) {
 504                  $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
 505                  $authuserid = isset($SESSION->$useridkey) ? $SESSION->$useridkey : 0;
 506                  if ($authuserid == $this->get_auth_user_id()) {
 507                      $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
 508                      if (isset($SESSION->$expireskey)) {
 509                          $expires = $SESSION->$expireskey;
 510                          if ($expires > time()) {
 511                              // We have a current access token for this user
 512                              // that has not expired.
 513                              return -1;
 514                          }
 515                      }
 516                  }
 517              } else {
 518                  $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
 519                  $authuserid = isset($SESSION->$useridkey) ? $SESSION->$useridkey : 0;
 520                  if (!empty($authuserid)) {
 521                      return $authuserid;
 522                  }
 523              }
 524          }
 525          return $this->curl_request('user', $this->email);
 526      }
 527  
 528      /**
 529       * Get all collections in this backpack.
 530       *
 531       * @return stdClass[] The collections.
 532       */
 533      public function get_collections() {
 534          global $PAGE;
 535  
 536          if ($this->authenticate()) {
 537              if ($this->backpackapiversion == OPEN_BADGES_V1) {
 538                  $result = $this->curl_request('groups');
 539                  if (isset($result->groups)) {
 540                      $result = $result->groups;
 541                  }
 542              } else {
 543                  $result = $this->curl_request('collections');
 544              }
 545              if ($result) {
 546                  return $result;
 547              }
 548          }
 549          return [];
 550      }
 551  
 552      /**
 553       * Get one collection by id.
 554       *
 555       * @param integer $collectionid
 556       * @return stdClass The collection.
 557       */
 558      public function get_collection_record($collectionid) {
 559          global $DB;
 560  
 561          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 562              return $DB->get_fieldset_select('badge_external', 'collectionid', 'backpackid = :bid', array('bid' => $collectionid));
 563          } else {
 564              return $DB->get_fieldset_select('badge_external', 'entityid', 'backpackid = :bid', array('bid' => $collectionid));
 565          }
 566      }
 567  
 568      /**
 569       * Disconnect the backpack from this user.
 570       *
 571       * @param integer $userid The user in Moodle
 572       * @param integer $backpackid The backpack to disconnect
 573       * @return boolean
 574       */
 575      public function disconnect_backpack($userid, $backpackid) {
 576          global $DB, $USER;
 577  
 578          if (\core\session\manager::is_loggedinas() || $userid != $USER->id) {
 579              // Can't change someone elses backpack settings.
 580              return false;
 581          }
 582  
 583          $badgescache = cache::make('core', 'externalbadges');
 584  
 585          $DB->delete_records('badge_external', array('backpackid' => $backpackid));
 586          $DB->delete_records('badge_backpack', array('userid' => $userid));
 587          $badgescache->delete($userid);
 588          return true;
 589      }
 590  
 591      /**
 592       * Handle the response from getting a collection to map to an id.
 593       *
 594       * @param stdClass $data The response data.
 595       * @return string The collection id.
 596       */
 597      public function get_collection_id_from_response($data) {
 598          if ($this->backpackapiversion == OPEN_BADGES_V1) {
 599              return $data->groupId;
 600          } else {
 601              return $data->entityId;
 602          }
 603      }
 604  
 605      /**
 606       * Get the last error message returned during an authentication request.
 607       *
 608       * @return string
 609       */
 610      public function get_authentication_error() {
 611          return backpack_api_mapping::get_authentication_error();
 612      }
 613  
 614      /**
 615       * Get the list of badges in a collection.
 616       *
 617       * @param stdClass $collection The collection to deal with.
 618       * @param boolean $expanded Fetch all the sub entities.
 619       * @return stdClass[]
 620       */
 621      public function get_badges($collection, $expanded = false) {
 622          global $PAGE;
 623  
 624          if ($this->authenticate()) {
 625              if ($this->backpackapiversion == OPEN_BADGES_V1) {
 626                  if (empty($collection->collectionid)) {
 627                      return [];
 628                  }
 629                  $result = $this->curl_request('badges', $collection->collectionid);
 630                  return $result->badges;
 631              } else {
 632                  if (empty($collection->entityid)) {
 633                      return [];
 634                  }
 635                  // Now we can make requests.
 636                  $badges = $this->curl_request('badges', $collection->entityid);
 637                  if (count($badges) == 0) {
 638                      return [];
 639                  }
 640                  $badges = $badges[0];
 641                  if ($expanded) {
 642                      $publicassertions = [];
 643                      $context = context_system::instance();
 644                      $output = $PAGE->get_renderer('core', 'badges');
 645                      foreach ($badges->assertions as $assertion) {
 646                          $remoteassertion = $this->get_assertion($assertion);
 647                          // Remote badge was fetched nested in the assertion.
 648                          $remotebadge = $remoteassertion->badgeclass;
 649                          if (!$remotebadge) {
 650                              continue;
 651                          }
 652                          $apidata = badgeclass_exporter::map_external_data($remotebadge, $this->backpackapiversion);
 653                          $exporterinstance = new badgeclass_exporter($apidata, ['context' => $context]);
 654                          $remotebadge = $exporterinstance->export($output);
 655  
 656                          $remoteissuer = $remotebadge->issuer;
 657                          $apidata = issuer_exporter::map_external_data($remoteissuer, $this->backpackapiversion);
 658                          $exporterinstance = new issuer_exporter($apidata, ['context' => $context]);
 659                          $remoteissuer = $exporterinstance->export($output);
 660  
 661                          $badgeclone = clone $remotebadge;
 662                          $badgeclone->issuer = $remoteissuer;
 663                          $remoteassertion->badge = $badgeclone;
 664                          $remotebadge->assertion = $remoteassertion;
 665                          $publicassertions[] = $remotebadge;
 666                      }
 667                      $badges = $publicassertions;
 668                  }
 669                  return $badges;
 670              }
 671          }
 672      }
 673  }