diff --git a/badges/backpack-add.php b/badges/backpack-add.php index 9c575203794ed..0342e55e063c5 100644 --- a/badges/backpack-add.php +++ b/badges/backpack-add.php @@ -48,7 +48,8 @@ $badgeid = $issuedbadge->badgeid; $badge = new badge($badgeid); $backpack = $DB->get_record('badge_backpack', array('userid' => $USER->id)); - $sitebackpack = badges_get_site_backpack($backpack->externalbackpackid); + $sitebackpack = badges_get_site_primary_backpack(); + $userbackpack = badges_get_site_backpack($backpack->externalbackpackid); $assertion = new core_badges_assertion($id, $sitebackpack->apiversion); $api = new \core_badges\backpack_api($sitebackpack); $api->authenticate(); @@ -61,7 +62,8 @@ throw new moodle_exception('invalidrequest', 'error'); } $issuerentityid = $response->id; - badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email'], $issuerentityid); + badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email'], + $issuerentityid, $response->openBadgeId); } // Create badge. $badge = $assertion->get_badge_class(false); @@ -72,25 +74,55 @@ throw new moodle_exception('invalidrequest', 'error'); } $badgeentityid = $response->id; - badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid, $badgeentityid); + badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid, + $badgeentityid, $response->hostedUrl); } // Create assertion (Award the badge!). $assertiondata = $assertion->get_badge_assertion(false, false); $assertionid = $assertion->get_assertion_hash(); + $assertionentityid = badges_external_get_mapping( + $sitebackpack->id, + OPEN_BADGES_V2_TYPE_ASSERTION, + $assertionid + ); - if (!($assertionentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid))) { + if (!$assertionentityid && strpos($sitebackpack->backpackapiurl, 'badgr')) { + $assertionentityid = badges_generate_badgr_open_url( + $sitebackpack, + OPEN_BADGES_V2_TYPE_ASSERTION, + $assertionentityid + ); + } + + // Create an assertion for the recipient in the issuer's account. + if (!$assertionentityid) { $response = $api->put_badgeclass_assertion($badgeentityid, $assertiondata); if (!$response) { throw new moodle_exception('invalidrequest', 'error'); } + $assertionentityid = badges_generate_badgr_open_url($sitebackpack, OPEN_BADGES_V2_TYPE_ASSERTION, $response->id); + badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid, + $response->id); + } + + // Now award/upload the badge to the user's account. + if ($assertionentityid && !badges_external_get_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid)) { + $userapi = new \core_badges\backpack_api($userbackpack, $backpack); + $userapi->authenticate(); + $response = $userapi->import_badge_assertion($assertionentityid); + if (!$response) { + throw new moodle_exception('invalidrequest', 'error'); + } $assertionentityid = $response->id; - badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid, $assertionentityid); + badges_external_create_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid, + $assertionentityid); $response = ['success' => 'addedtobackpack']; } else { $response = ['warning' => 'existsinbackpack']; } + redirect(new moodle_url('/badges/mybadges.php', $response)); } else { redirect(new moodle_url('/badges/mybadges.php')); diff --git a/badges/classes/backpack_api.php b/badges/classes/backpack_api.php index b25b39f9ef1dc..d15544340f966 100644 --- a/badges/classes/backpack_api.php +++ b/badges/classes/backpack_api.php @@ -87,7 +87,6 @@ public function __construct($sitebackpack, $userbackpack = false) { global $CFG; $admin = get_admin(); - $this->backpackapiurl = $sitebackpack->backpackapiurl; $this->backpackapiurl = $sitebackpack->backpackapiurl; $this->backpackapiversion = $sitebackpack->apiversion; $this->password = $sitebackpack->password; @@ -152,6 +151,21 @@ private function define_mappings() { true, // JSON Encoded. true // Auth required. ]; + $mapping[] = [ + 'importbadge', // Action. + // Badgr.io does not return the public information about a badge + // if the issuer is associated with another user. We need to pass + // the expand parameters which are not in any specification to get + // additional information about the assertion in a single request. + '[URL]/backpack/import', + ['url' => '[PARAM]'], // Post params. + '', // Request exporter. + 'core_badges\external\assertion_exporter', // Response exporter. + false, // Multiple. + 'post', // Method. + true, // JSON Encoded. + true // Auth required. + ]; $mapping[] = [ 'badges', // Action. '[URL]/backpack/collections/[PARAM1]', // URL @@ -408,6 +422,22 @@ public function put_badgeclass_assertion($entityid, $data) { return $this->curl_request('assertions', null, $entityid, $data); } + /** + * Import a badge assertion into a backpack. This is used to handle cross domain backpacks. + * + * @param string $data The structure of the badge class assertion. + * @return mixed + * @throws coding_exception + */ + public function import_badge_assertion(string $data) { + // V2 Only. + if ($this->backpackapiversion == OPEN_BADGES_V1) { + throw new coding_exception('Not supported in this backpack API'); + } + + return $this->curl_request('importbadge', null, null, $data); + } + /** * Select collections from a backpack. * @@ -570,7 +600,6 @@ public function get_collection_record($collectionid) { * * @param integer $userid The user in Moodle * @param integer $backpackid The backpack to disconnect - * @param integer $externalbackupid The external backpack to disconnect * @return boolean */ public function disconnect_backpack($userid, $backpackid, $externalbackupid) { diff --git a/badges/classes/backpack_api_mapping.php b/badges/classes/backpack_api_mapping.php index efd57c4d90704..5e70fe57039a1 100644 --- a/badges/classes/backpack_api_mapping.php +++ b/badges/classes/backpack_api_mapping.php @@ -209,6 +209,8 @@ private function get_post_params($email, $password, $param) { } else if ($value == '[PASSWORD]') { $value = $password; $request[$key] = $value; + } else if ($value == '[PARAM]') { + $request[$key] = is_array($param) ? $param[0] : $param; } } } @@ -312,6 +314,7 @@ private function get_curl_options() { return array( 'FRESH_CONNECT' => true, 'RETURNTRANSFER' => true, + 'FOLLOWLOCATION' => true, 'FORBID_REUSE' => true, 'HEADER' => 0, 'CONNECTTIMEOUT' => 3, diff --git a/badges/renderer.php b/badges/renderer.php index 28a182bed4ef2..2d0d3f9e2d8f5 100644 --- a/badges/renderer.php +++ b/badges/renderer.php @@ -650,10 +650,6 @@ protected function render_badge_user_collection(\core_badges\output\badge_user_c $externalhtml .= html_writer::start_tag('div', array('class' => 'generalbox')); $externalhtml .= $this->output->heading_with_help(get_string('externalbadges', 'badges'), 'externalbadges', 'badges'); if (!is_null($backpack)) { - if ($backpack->backpackid != $CFG->badges_site_backpack) { - $externalhtml .= $this->output->notification(get_string('backpackneedsupdate', 'badges'), 'warning'); - - } if ($backpack->totalcollections == 0) { $externalhtml .= get_string('nobackpackcollectionssummary', 'badges', $backpack); } else { diff --git a/badges/upgrade.txt b/badges/upgrade.txt index faca6035d4846..2ca56f3e77c56 100644 --- a/badges/upgrade.txt +++ b/badges/upgrade.txt @@ -1,6 +1,22 @@ This files describes API changes in /badges/*, information provided here is intended especially for developers. +=== 3.10 === +* Users can now specify a backpack that differs from the site backpack. In order to do this, connection details need to +be set in 'Manage backpacks' with OR without auth details. +* Introduced new functions in backpack_api +** 'import_badge_assertion' to facilitate cross domain badge imports. +** 'update_assertion' updates a previously defined/created assertion. +* New badge lib functions introduced +** badges_save_external_backpack() - This method handles inserts/updates to the site wide backpacks' configuration details. +** badges_save_backpack_credentials() - This method handles inserts/updates any authentication details to connect to the backpacks created. This can either be site OR user backpack authentication details +** badges_get_user_backpack() - Gets a specific user's backpack. Defaults to current user's backpack if none provided. +** badges_get_site_primary_backpack() - Get the primary backpack set for the site as defined in $CFG->badges_site_backpack +* badges_open_badges_backpack_api() - Now accepts a backpackid(badge_external_backpack id) to check whether the version of the provided backpack. + This was introduced because now there is a difference between a site and user backpack. If null, defaults to site_backpack. +* badges_get_site_backpack() - Accepts an additional $userid param if we want to get a specific user's backpack. Defaults to 0 if we are trying to get the site/admin level backpack +* badges_external_get_mapping() - Accepts an additional argument to indicate which value it wants returned. Defaults to 'externalid' which contains the OBv2 badge URL + === 3.9 === * BADGE_BACKPACKAPIURL and BADGE_BACKPACKWEBURL are deprecated and should not be used. * OBv2 has been set to the default value when the obversion is not defined. diff --git a/lib/badgeslib.php b/lib/badgeslib.php index 4b54cb6b7f3da..1fc0b46d44293 100644 --- a/lib/badgeslib.php +++ b/lib/badgeslib.php @@ -936,6 +936,17 @@ function badges_get_site_backpack($id) { return $DB->get_record('badge_external_backpack', ['id' => $id]); } +/** + * Get the primary backpack for the site + * + * @return array(stdClass) + */ +function badges_get_site_primary_backpack() { + global $CFG; + + return badges_get_site_backpack($CFG->badges_site_backpack); +} + /** * List the backpacks at site level. * @@ -1026,9 +1037,10 @@ function badges_disconnect_user_backpack($userid) { * @param integer $sitebackpackid The site backpack to connect to. * @param string $type The type of this remote object. * @param string $internalid The id for this object on the Moodle site. + * @param string $param The param we need to return. Defaults to the externalid. * @return mixed The id or false if it doesn't exist. */ -function badges_external_get_mapping($sitebackpackid, $type, $internalid) { +function badges_external_get_mapping($sitebackpackid, $type, $internalid, $param = 'externalid') { global $DB; // Return externalid if it exists. $params = [ @@ -1037,9 +1049,9 @@ function badges_external_get_mapping($sitebackpackid, $type, $internalid) { 'internalid' => $internalid ]; - $record = $DB->get_record('badge_external_identifier', $params, 'externalid', IGNORE_MISSING); + $record = $DB->get_record('badge_external_identifier', $params, $param, IGNORE_MISSING); if ($record) { - return $record->externalid; + return $record->$param; } return false; } @@ -1320,3 +1332,26 @@ function badges_get_oauth2_service_options() { return $options; } + +/** + * Generate a public badgr URL that conforms to OBv2. This is done because badgr responses do not currently conform to + * the spec. + * + * WARNING: This is an extremely hacky way of implementing this and should be removed once the standards are conformed to. + * + * @param stdClass $backpack The Badgr backpack we are pushing to + * @param string $type The type of object we are dealing with either Issuer, Assertion OR Badge. + * @param string $externalid The externalid as provided by the backpack + * @return string The public URL to access Badgr objects + */ +function badges_generate_badgr_open_url($backpack, $type, $externalid) { + if (badges_open_badges_backpack_api($backpack->id) == OPEN_BADGES_V2) { + $entity = strtolower($type); + if ($type == OPEN_BADGES_V2_TYPE_BADGE) { + $entity = "badge"; + } + $url = new moodle_url($backpack->backpackapiurl); + return "{$url->get_scheme()}://{$url->get_host()}/public/{$entity}s/$externalid"; + + } +} \ No newline at end of file diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index c52e2f41b1a78..97f0a58d66d7b 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2868,9 +2868,6 @@ function xmldb_main_upgrade($oldversion) { if (!$dbman->field_exists($table, $field)) { $dbman->add_field($table, $field); } - $table->deleteKey('backpackapiurlkey'); - $table->deleteKey('backpackweburlkey'); - $table->add_key('backpackapiurlkey', XMLDB_KEY_UNIQUE, ['backpackapiurl', 'backpackemail']); // Main savepoint reached. upgrade_main_savepoint(true, 2020102300.01);