MDL-63262 badges: Add badgr.io
[moodle.git] / badges / classes / backpack_api_mapping.php
CommitLineData
aae219ac
DW
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 * Represent the url for each method and the encoding of the parameters and response.
19 *
20 * @package core_badges
21 * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 * @author Yuliya Bozhko <yuliya.bozhko@totaralms.com>
24 */
25
26namespace core_badges;
27
28defined('MOODLE_INTERNAL') || die();
29
30global $CFG;
31require_once($CFG->libdir . '/filelib.php');
32
33use context_system;
34use core_badges\external\assertion_exporter;
35use core_badges\external\collection_exporter;
36use core_badges\external\issuer_exporter;
37use core_badges\external\badgeclass_exporter;
38use curl;
39
40/**
41 * Represent a single method for the remote api.
42 */
43class backpack_api_mapping {
44
45 /** @var string The action of this method. */
46 public $action;
47
48 /** @var string The base url of this backpack. */
49 private $url;
50
51 /** @var array List of parameters for this method. */
52 public $params;
53
54 /** @var string Name of a class to export parameters for this method. */
55 public $requestexporter;
56
57 /** @var string Name of a class to export response for this method. */
58 public $responseexporter;
59
60 /** @var boolean This method returns an array of responses. */
61 public $multiple;
62
63 /** @var string get or post methods. */
64 public $method;
65
66 /** @var boolean json decode the response. */
67 public $json;
68
69 /** @var boolean Authentication is required for this request. */
70 public $authrequired;
71
72 /** @var boolean Differentiate the function that can be called on a user backpack or a site backpack. */
73 private $isuserbackpack;
74
75 /**
76 * Create a mapping.
77 *
78 * @param string $action The action of this method.
79 * @param string $url The base url of this backpack.
80 * @param mixed $postparams List of parameters for this method.
81 * @param string $requestexporter Name of a class to export parameters for this method.
82 * @param string $responseexporter Name of a class to export response for this method.
83 * @param boolean $multiple This method returns an array of responses.
84 * @param string $method get or post methods.
85 * @param boolean $json json decode the response.
86 * @param boolean $authrequired Authentication is required for this request.
87 * @param boolean $isuserbackpack user backpack or a site backpack.
88 * @param integer $backpackapiversion OpenBadges version 1 or 2.
89 */
90 public function __construct($action, $url, $postparams, $requestexporter, $responseexporter,
91 $multiple, $method, $json, $authrequired, $isuserbackpack, $backpackapiversion) {
92 $this->action = $action;
93 $this->url = $url;
94 $this->postparams = $postparams;
95 $this->requestexporter = $requestexporter;
96 $this->responseexporter = $responseexporter;
97 $this->multiple = $multiple;
98 $this->method = $method;
99 $this->json = $json;
100 $this->authrequired = $authrequired;
101 $this->isuserbackpack = $isuserbackpack;
102 $this->backpackapiversion = $backpackapiversion;
103 }
104
105 /**
106 * Get the unique key for the token.
107 *
108 * @param string $type The type of token.
109 * @return string
110 */
111 private function get_token_key($type) {
112 $prefix = 'badges_';
113 if ($this->isuserbackpack) {
114 $prefix .= 'user_backpack_';
115 } else {
116 $prefix .= 'site_backpack_';
117 }
118 $prefix .= $type . '_token';
119 return $prefix;
120 }
121
122 /**
123 * Does the action match this mapping?
124 *
125 * @param string $action The action.
126 * @return boolean
127 */
128 public function is_match($action) {
129 return $this->action == $action;
130 }
131
132 /**
133 * Parse the method url and insert parameters.
134 *
135 * @param string $apiurl The raw apiurl.
136 * @param string $param1 The first parameter.
137 * @param string $param2 The second parameter.
138 * @return string
139 */
140 private function get_url($apiurl, $param1, $param2) {
141 $urlscheme = parse_url($apiurl, PHP_URL_SCHEME);
142 $urlhost = parse_url($apiurl, PHP_URL_HOST);
143
144 $url = $this->url;
145 $url = str_replace('[SCHEME]', $urlscheme, $url);
146 $url = str_replace('[HOST]', $urlhost, $url);
147 $url = str_replace('[URL]', $apiurl, $url);
148 $url = str_replace('[PARAM1]', $param1, $url);
149 $url = str_replace('[PARAM2]', $param2, $url);
150
151 return $url;
152 }
153
154 /**
155 * Parse the post parameters and insert replacements.
156 *
157 * @param string $email The api username.
158 * @param string $password The api password.
159 * @param string $param The parameter.
160 * @return mixed
161 */
162 private function get_post_params($email, $password, $param) {
163 global $PAGE;
164
165 if ($this->method == 'get') {
166 return '';
167 }
168
169 $request = $this->postparams;
170 if ($request === '[PARAM]') {
171 $value = $param;
172 foreach ($value as $key => $keyvalue) {
173 if (gettype($value[$key]) == 'array') {
174 $newkey = 'related_' . $key;
175 $value[$newkey] = $value[$key];
176 unset($value[$key]);
177 }
178 }
179 } else if (is_array($request)) {
180 foreach ($request as $key => $value) {
181 if ($value == '[EMAIL]') {
182 $value = $email;
183 $request[$key] = $value;
184 } else if ($value == '[PASSWORD]') {
185 $value = $password;
186 $request[$key] = $value;
187 }
188 }
189 }
190 $context = context_system::instance();
191 $exporter = $this->requestexporter;
192 $output = $PAGE->get_renderer('core', 'badges');
193 if (!empty($exporter)) {
194 $exporterinstance = new $exporter($value, ['context' => $context]);
195 $request = $exporterinstance->export($output);
196 }
197 if ($this->json) {
198 return json_encode($request);
199 }
200 return $request;
201 }
202
203 /**
204 * Read the response from a V1 user request and save the userID.
205 *
206 * @param string $response The request response.
207 * @param integer $backpackid The backpack id.
208 * @return boolean
209 */
210 private function convert_email_response($response, $backpackid) {
211 global $SESSION;
212
213 if (isset($response->status) && $response->status == 'okay') {
214
215 // Remember the tokens.
216 $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
217 $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
218
219 $SESSION->$useridkey = $response->userId;
220 $SESSION->$backpackidkey = $backpackid;
221 return $response->userId;
222 }
223 return false;
224 }
225
226 /**
227 * Get the user id from a previous user request.
228 *
229 * @return integer
230 */
231 private function get_auth_user_id() {
232 global $USER;
233
234 if ($this->isuserbackpack) {
235 return $USER->id;
236 } else {
237 // The access tokens for the system backpack are shared.
238 return -1;
239 }
240 }
241
242 /**
243 * Parse the response from an openbadges 2 login.
244 *
245 * @param string $response The request response data.
246 * @param integer $backpackid The id of the backpack.
247 * @return mixed
248 */
249 private function oauth_token_response($response, $backpackid) {
250 global $SESSION;
251
252 if (isset($response->access_token) && isset($response->refresh_token)) {
253 // Remember the tokens.
254 $accesskey = $this->get_token_key(BADGE_ACCESS_TOKEN);
255 $refreshkey = $this->get_token_key(BADGE_REFRESH_TOKEN);
256 $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
257 $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
258 $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
259 if (isset($response->expires_in)) {
260 $timeout = $response->expires_in;
261 } else {
262 $timeout = 15 * 60; // 15 minute timeout if none set.
263 }
264 $expires = $timeout + time();
265
266 $SESSION->$expireskey = $expires;
267 $SESSION->$useridkey = $this->get_auth_user_id();
268 $SESSION->$accesskey = $response->access_token;
269 $SESSION->$refreshkey = $response->refresh_token;
270 $SESSION->$backpackidkey = $backpackid;
271 return -1;
272 }
273 return $response;
274 }
275
276 /**
277 * Standard options used for all curl requests.
278 *
279 * @return array
280 */
281 private function get_curl_options() {
282 return array(
283 'FRESH_CONNECT' => true,
284 'RETURNTRANSFER' => true,
285 'FORBID_REUSE' => true,
286 'HEADER' => 0,
287 'CONNECTTIMEOUT' => 3,
288 'CONNECTTIMEOUT' => 3,
289 // Follow redirects with the same type of request when sent 301, or 302 redirects.
290 'CURLOPT_POSTREDIR' => 3,
291 );
292 }
293
294 /**
295 * Make an api request and parse the response.
296 *
297 * @param string $apiurl Raw request url.
298 * @param string $urlparam1 Parameter for the request.
299 * @param string $urlparam2 Parameter for the request.
300 * @param string $email User email for authentication.
301 * @param string $password for authentication.
302 * @param mixed $postparam Raw data for the post body.
303 * @param string $backpackid the id of the backpack to use.
304 * @return mixed
305 */
306 public function request($apiurl, $urlparam1, $urlparam2, $email, $password, $postparam, $backpackid) {
307 global $SESSION, $PAGE;
308
309 $curl = new curl();
310
311 $url = $this->get_url($apiurl, $urlparam1, $urlparam2);
312
313 if ($this->authrequired) {
314 $accesskey = $this->get_token_key(BADGE_ACCESS_TOKEN);
315 if (isset($SESSION->$accesskey)) {
316 $token = $SESSION->$accesskey;
317 $curl->setHeader('Authorization: Bearer ' . $token);
318 }
319 }
320 if ($this->json) {
321 $curl->setHeader(array('Content-type: application/json'));
322 }
323 $curl->setHeader(array('Accept: application/json', 'Expect:'));
324 $options = $this->get_curl_options();
325
326 $post = $this->get_post_params($email, $password, $postparam);
327 if ($this->method == 'get') {
328 $response = $curl->get($url, $post, $options);
329 } else if ($this->method == 'post') {
330 $response = $curl->post($url, $post, $options);
331 }
332 $response = json_decode($response);
333
334 if (isset($response->result)) {
335 $response = $response->result;
336 }
337 $context = context_system::instance();
338 $exporter = $this->responseexporter;
339 if (class_exists($exporter)) {
340 $output = $PAGE->get_renderer('core', 'badges');
341 if (!$this->multiple) {
342 if (count($response)) {
343 $response = $response[0];
344 }
345 if (empty($response)) {
346 return null;
347 }
348 $apidata = $exporter::map_external_data($response, $this->backpackapiversion);
349 $exporterinstance = new $exporter($apidata, ['context' => $context]);
350 $data = $exporterinstance->export($output);
351 return $data;
352 } else {
353 $multiple = [];
354 if (empty($response)) {
355 return $multiple;
356 }
357 foreach ($response as $data) {
358 $apidata = $exporter::map_external_data($data, $this->backpackapiversion);
359 $exporterinstance = new $exporter($apidata, ['context' => $context]);
360 $multiple[] = $exporterinstance->export($output);
361 }
362 return $multiple;
363 }
364 } else if (method_exists($this, $exporter)) {
365 return $this->$exporter($response, $backpackid);
366 }
367 return $response;
368 }
369}