See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [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 2020 Tung Thai based on Totara Learning Solutions Ltd {@link http://www.totaralms.com/} dode 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.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 context_system; 34 use moodle_url; 35 use core_badges\backpack_api2p1_mapping; 36 use core_badges\oauth2\client; 37 use curl; 38 use stdClass; 39 use core\oauth2\issuer; 40 use core\oauth2\endpoint; 41 use core\oauth2\discovery\imsbadgeconnect; 42 43 /** 44 * To process badges with backpack and control api request and this class using for Open Badge API v2.1 methods. 45 * 46 * @package core_badges 47 * @copyright 2020 Tung Thai 48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 49 */ 50 class backpack_api2p1 { 51 52 /** @var object is the external backpack. */ 53 private $externalbackpack; 54 55 /** @var array define api mapping. */ 56 private $mappings = []; 57 58 /** @var false|null|stdClass|\core_badges\backpack_api2p1 to */ 59 private $tokendata; 60 61 /** @var null clienid. */ 62 private $clientid = null; 63 64 /** @var null version api of the backpack. */ 65 protected $backpackapiversion; 66 67 /** @var issuer The OAuth2 Issuer for this backpack */ 68 protected issuer $issuer; 69 70 /** @var endpoint The apiBase endpoint */ 71 protected endpoint $apibase; 72 73 /** 74 * backpack_api2p1 constructor. 75 * 76 * @param object $externalbackpack object 77 * @throws coding_exception error message 78 */ 79 public function __construct($externalbackpack) { 80 81 if (!empty($externalbackpack)) { 82 $this->externalbackpack = $externalbackpack; 83 $this->backpackapiversion = $externalbackpack->apiversion; 84 $this->get_clientid = $this->get_clientid($externalbackpack->oauth2_issuerid); 85 86 if (!($this->tokendata = $this->get_stored_token($externalbackpack->id)) 87 && $this->backpackapiversion != OPEN_BADGES_V2P1) { 88 throw new coding_exception('Backpack incorrect'); 89 } 90 } 91 92 $this->define_mappings(); 93 } 94 95 /** 96 * Initialises or returns the OAuth2 issuer associated to this backpack. 97 * 98 * @return issuer 99 */ 100 protected function get_issuer(): issuer { 101 if (!isset($this->issuer)) { 102 $this->issuer = new \core\oauth2\issuer($this->externalbackpack->oauth2_issuerid); 103 } 104 return $this->issuer; 105 } 106 107 /** 108 * Gets the apiBase url associated to this backpack. 109 * 110 * @return string 111 */ 112 protected function get_api_base_url(): string { 113 if (!isset($this->apibase)) { 114 $apibase = endpoint::get_record([ 115 'issuerid' => $this->externalbackpack->oauth2_issuerid, 116 'name' => 'apiBase', 117 ]); 118 119 if (empty($apibase)) { 120 imsbadgeconnect::create_endpoints($this->get_issuer()); 121 $apibase = endpoint::get_record([ 122 'issuerid' => $this->externalbackpack->oauth2_issuerid, 123 'name' => 'apiBase', 124 ]); 125 } 126 127 $this->apibase = $apibase; 128 } 129 130 return $this->apibase->get('url'); 131 } 132 133 134 /** 135 * Define the mappings supported by this usage and api version. 136 */ 137 private function define_mappings() { 138 if ($this->backpackapiversion == OPEN_BADGES_V2P1) { 139 140 $mapping = []; 141 $mapping[] = [ 142 'post.assertions', // Action. 143 '[URL]/assertions', // URL 144 '[PARAM]', // Post params. 145 false, // Multiple. 146 'post', // Method. 147 true, // JSON Encoded. 148 true // Auth required. 149 ]; 150 151 $mapping[] = [ 152 'get.assertions', // Action. 153 '[URL]/assertions', // URL 154 '[PARAM]', // Post params. 155 false, // Multiple. 156 'get', // Method. 157 true, // JSON Encoded. 158 true // Auth required. 159 ]; 160 161 foreach ($mapping as $map) { 162 $map[] = false; // Site api function. 163 $map[] = OPEN_BADGES_V2P1; // V2 function. 164 $this->mappings[] = new backpack_api2p1_mapping(...$map); 165 } 166 167 } 168 } 169 170 /** 171 * Disconnect the backpack from this user. 172 * 173 * @param object $backpack to disconnect. 174 * @return bool 175 * @throws \dml_exception 176 */ 177 public function disconnect_backpack($backpack) { 178 global $USER, $DB; 179 180 if ($backpack) { 181 $DB->delete_records_select('badge_external', 'backpackid = :backpack', ['backpack' => $backpack->id]); 182 $DB->delete_records('badge_backpack', ['id' => $backpack->id]); 183 $DB->delete_records('badge_backpack_oauth2', ['externalbackpackid' => $this->externalbackpack->id, 184 'userid' => $USER->id]); 185 186 return true; 187 } 188 return false; 189 } 190 191 /** 192 * Make an api request. 193 * 194 * @param string $action The api function. 195 * @param string $postdata The body of the api request. 196 * @return mixed 197 */ 198 public function curl_request($action, $postdata = null) { 199 $tokenkey = $this->tokendata->token; 200 foreach ($this->mappings as $mapping) { 201 if ($mapping->is_match($action)) { 202 return $mapping->request( 203 $this->get_api_base_url(), 204 $tokenkey, 205 $postdata 206 ); 207 } 208 } 209 210 throw new coding_exception('Unknown request'); 211 } 212 213 /** 214 * Get token. 215 * 216 * @param int $externalbackpackid ID of external backpack. 217 * @return oauth2\badge_backpack_oauth2|false|stdClass|null 218 */ 219 protected function get_stored_token($externalbackpackid) { 220 global $USER; 221 222 $token = \core_badges\oauth2\badge_backpack_oauth2::get_record( 223 ['externalbackpackid' => $externalbackpackid, 'userid' => $USER->id]); 224 if ($token !== false) { 225 $token = $token->to_record(); 226 return $token; 227 } 228 return null; 229 } 230 231 /** 232 * Get client id. 233 * 234 * @param int $issuerid id of Oauth2 service. 235 * @throws coding_exception 236 */ 237 private function get_clientid($issuerid) { 238 $issuer = \core\oauth2\api::get_issuer($issuerid); 239 if (!empty($issuer)) { 240 $this->issuer = $issuer; 241 $this->clientid = $issuer->get('clientid'); 242 } 243 } 244 245 /** 246 * Export a badge to the backpack site. 247 * 248 * @param string $hash of badge issued. 249 * @return array 250 * @throws \moodle_exception 251 * @throws coding_exception 252 */ 253 public function put_assertions($hash) { 254 $data = []; 255 if (!$hash) { 256 return false; 257 } 258 259 $issuer = $this->get_issuer(); 260 $client = new client($issuer, new moodle_url('/badges/mybadges.php'), '', $this->externalbackpack); 261 if (!$client->is_logged_in()) { 262 $redirecturl = new moodle_url('/badges/mybadges.php', ['error' => 'backpackexporterror']); 263 redirect($redirecturl); 264 } 265 266 $this->tokendata = $this->get_stored_token($this->externalbackpack->id); 267 268 $assertion = new \core_badges_assertion($hash, OPEN_BADGES_V2); 269 $data['assertion'] = $assertion->get_badge_assertion(); 270 $response = $this->curl_request('post.assertions', $data); 271 if ($response && isset($response->status->statusCode) && $response->status->statusCode == 200) { 272 $msg['status'] = \core\output\notification::NOTIFY_SUCCESS; 273 $msg['message'] = get_string('addedtobackpack', 'badges'); 274 } else { 275 if ($response) { 276 // Although the specification defines that status error is a string, some providers, like Badgr, are wrongly 277 // returning an array. It has been reported, but adding these extra checks doesn't hurt, just in case. 278 if ( 279 property_exists($response, 'status') && 280 is_object($response->status) && 281 property_exists($response->status, 'error') 282 ) { 283 $statuserror = $response->status->error; 284 if (is_array($statuserror)) { 285 $statuserror = implode($statuserror); 286 } 287 } else if (property_exists($response, 'error')) { 288 $statuserror = $response->error; 289 if (property_exists($response, 'message')) { 290 $statuserror .= '. Message: ' . $response->message; 291 } 292 } 293 } else { 294 $statuserror = 'Empty response'; 295 } 296 $data = [ 297 'badgename' => $data['assertion']['badge']['name'], 298 'error' => $statuserror, 299 ]; 300 301 $msg['status'] = \core\output\notification::NOTIFY_ERROR; 302 $msg['message'] = get_string('backpackexporterrorwithinfo', 'badges', $data); 303 } 304 return $msg; 305 } 306 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body