Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
namespace core_badges;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
require_once($CFG->libdir . '/filelib.php');
22
 
23
use cache;
24
use coding_exception;
25
use core_badges\external\issuer_exporter;
26
use core_badges\external\badgeclass_exporter;
27
use stdClass;
28
use context_system;
29
 
30
define('BADGE_ACCESS_TOKEN', 'access');
31
define('BADGE_USER_ID_TOKEN', 'user_id');
32
define('BADGE_BACKPACK_ID_TOKEN', 'backpack_id');
33
define('BADGE_REFRESH_TOKEN', 'refresh');
34
define('BADGE_EXPIRES_TOKEN', 'expires');
35
 
36
/**
37
 * Class for communicating with backpacks.
38
 *
1441 ariadna 39
 * @package    core_badges
40
 * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
1 efrain 41
 * @copyright  2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
42
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43
 */
44
class backpack_api {
1441 ariadna 45
    /** @var int Canvas Credentials backpack provider */
46
    public const PROVIDER_CANVAS_CREDENTIALS = 0;
1 efrain 47
 
1441 ariadna 48
    /** @var int Other backpack provider */
49
    public const PROVIDER_OTHER = 1;
50
 
51
    /** @var int Empty provider */
52
    public const PROVIDER_EMPTY = -1;
53
 
54
    /** @var int Empty region */
55
    public const REGION_EMPTY = -1;
56
 
1 efrain 57
    /** @var string The email address of the issuer or the backpack owner. */
58
    private $email;
59
 
60
    /** @var string The base url used for api requests to this backpack. */
61
    private $backpackapiurl;
62
 
63
    /** @var integer The backpack api version to use. */
64
    private $backpackapiversion;
65
 
66
    /** @var string The password to authenticate requests. */
67
    private $password;
68
 
69
    /** @var boolean User or site api requests. */
70
    private $isuserbackpack;
71
 
72
    /** @var integer The id of the backpack we are talking to. */
73
    private $backpackid;
74
 
1441 ariadna 75
    /** @var \core_badges\backpack_api_mapping[] List of apis for the user or site using api version 2. */
1 efrain 76
    private $mappings = [];
77
 
78
    /**
79
     * Create a wrapper to communicate with the backpack.
80
     *
81
     * The resulting class can only do either site backpack communication or
82
     * user backpack communication.
83
     *
84
     * @param stdClass $sitebackpack The site backpack record
85
     * @param mixed $userbackpack Optional - if passed it represents the users backpack.
86
     */
87
    public function __construct($sitebackpack, $userbackpack = false) {
88
        global $CFG;
89
        $admin = get_admin();
90
 
91
        $this->backpackapiurl = $sitebackpack->backpackapiurl;
92
        $this->backpackapiversion = $sitebackpack->apiversion;
93
        $this->password = $sitebackpack->password;
94
        $this->email = $sitebackpack->backpackemail;
95
        $this->isuserbackpack = false;
96
        $this->backpackid = $sitebackpack->id;
97
        if (!empty($userbackpack)) {
98
            $this->isuserbackpack = true;
99
            $this->password = $userbackpack->password;
100
            $this->email = $userbackpack->email;
101
        }
102
 
103
        $this->define_mappings();
104
        // Clear the last authentication error.
105
        backpack_api_mapping::set_authentication_error('');
106
    }
107
 
108
    /**
109
     * Define the mappings supported by this usage and api version.
110
     */
111
    private function define_mappings() {
1441 ariadna 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);
1 efrain 181
            }
182
        } else {
1441 ariadna 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);
1 efrain 243
            }
244
        }
245
    }
246
 
247
    /**
248
     * Make an api request
249
     *
250
     * @param string $action The api function.
251
     * @param string $collection An api parameter
252
     * @param string $entityid An api parameter
253
     * @param string $postdata The body of the api request.
254
     * @return mixed
255
     */
256
    private function curl_request($action, $collection = null, $entityid = null, $postdata = null) {
257
        foreach ($this->mappings as $mapping) {
258
            if ($mapping->is_match($action)) {
259
                return $mapping->request(
260
                    $this->backpackapiurl,
261
                    $collection,
262
                    $entityid,
263
                    $this->email,
264
                    $this->password,
265
                    $postdata,
266
                    $this->backpackid
267
                );
268
            }
269
        }
270
 
271
        throw new coding_exception('Unknown request');
272
    }
273
 
274
    /**
275
     * Get the id to use for requests with this api.
276
     *
277
     * @return integer
278
     */
279
    private function get_auth_user_id() {
280
        global $USER;
281
 
282
        if ($this->isuserbackpack) {
283
            return $USER->id;
284
        } else {
285
            // The access tokens for the system backpack are shared.
286
            return -1;
287
        }
288
    }
289
 
290
    /**
291
     * Get the name of the key to store this access token type.
292
     *
293
     * @param string $type
294
     * @return string
295
     */
296
    private function get_token_key($type) {
297
        // This should be removed when everything has a mapping.
298
        $prefix = 'badges_';
299
        if ($this->isuserbackpack) {
300
            $prefix .= 'user_backpack_';
301
        } else {
302
            $prefix .= 'site_backpack_';
303
        }
304
        $prefix .= $type . '_token';
305
        return $prefix;
306
    }
307
 
308
    /**
309
     * Make an api request to get an assertion
310
     *
311
     * @param string $entityid The id of the assertion.
312
     * @return mixed
313
     */
314
    public function get_assertion($entityid) {
315
        return $this->curl_request('assertion', null, $entityid);
316
    }
317
 
318
    /**
319
     * Create a badgeclass assertion.
320
     *
321
     * @param string $entityid The id of the badge class.
322
     * @param string $data The structure of the badge class assertion.
323
     * @return mixed
324
     */
325
    public function put_badgeclass_assertion($entityid, $data) {
326
        return $this->curl_request('assertions', null, $entityid, $data);
327
    }
328
 
329
    /**
330
     * Update a badgeclass assertion.
331
     *
332
     * @param string $entityid The id of the badge class.
333
     * @param array $data The structure of the badge class assertion.
334
     * @return mixed
335
     */
336
    public function update_assertion(string $entityid, array $data) {
337
        return $this->curl_request('updateassertion', null, $entityid, $data);
338
    }
339
 
340
    /**
341
     * Import a badge assertion into a backpack. This is used to handle cross domain backpacks.
342
     *
343
     * @param string $data The structure of the badge class assertion.
344
     * @return mixed
345
     * @throws coding_exception
346
     */
347
    public function import_badge_assertion(string $data) {
348
        return $this->curl_request('importbadge', null, null, $data);
349
    }
350
 
351
    /**
352
     * Select collections from a backpack.
353
     *
354
     * @param string $backpackid The id of the backpack
355
     * @param stdClass[] $collections List of collections with collectionid or entityid.
356
     * @return boolean
357
     */
358
    public function set_backpack_collections($backpackid, $collections) {
359
        global $DB, $USER;
360
 
361
        // Delete any previously selected collections.
362
        $sqlparams = array('backpack' => $backpackid);
363
        $select = 'backpackid = :backpack ';
364
        $DB->delete_records_select('badge_external', $select, $sqlparams);
365
        $badgescache = cache::make('core', 'externalbadges');
366
 
367
        // Insert selected collections if they are not in database yet.
368
        foreach ($collections as $collection) {
369
            $obj = new stdClass();
370
            $obj->backpackid = $backpackid;
1441 ariadna 371
            $obj->entityid = $collection;
372
            $obj->collectionid = -1;
1 efrain 373
            if (!$DB->record_exists('badge_external', (array) $obj)) {
374
                $DB->insert_record('badge_external', $obj);
375
            }
376
        }
377
        $badgescache->delete($USER->id);
378
        return true;
379
    }
380
 
381
    /**
382
     * Create a badgeclass
383
     *
384
     * @param string $entityid The id of the entity.
385
     * @param string $data The structure of the badge class.
386
     * @return mixed
387
     */
388
    public function put_badgeclass($entityid, $data) {
389
        return $this->curl_request('badgeclasses', null, $entityid, $data);
390
    }
391
 
392
    /**
393
     * Create an issuer
394
     *
395
     * @param string $data The structure of the issuer.
396
     * @return mixed
397
     */
398
    public function put_issuer($data) {
399
        return $this->curl_request('issuers', null, null, $data);
400
    }
401
 
402
    /**
403
     * Delete any user access tokens in the session so we will attempt to get new ones.
404
     *
405
     * @return void
406
     */
407
    public function clear_system_user_session() {
408
        global $SESSION;
409
 
410
        $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
411
        unset($SESSION->$useridkey);
412
 
413
        $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
414
        unset($SESSION->$expireskey);
415
    }
416
 
417
    /**
418
     * Authenticate using the stored email and password and save the valid access tokens.
419
     *
420
     * @return mixed The id of the authenticated user as returned by the backpack. Can have
421
     *    different formats - numeric, empty, object with 'error' property, etc.
422
     */
423
    public function authenticate() {
424
        global $SESSION;
425
 
426
        $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
427
        $backpackid = isset($SESSION->$backpackidkey) ? $SESSION->$backpackidkey : 0;
428
        // If the backpack is changed we need to expire sessions.
429
        if ($backpackid == $this->backpackid) {
430
            if ($this->backpackapiversion == OPEN_BADGES_V2) {
431
                $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
432
                $authuserid = isset($SESSION->$useridkey) ? $SESSION->$useridkey : 0;
433
                if ($authuserid == $this->get_auth_user_id()) {
434
                    $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
435
                    if (isset($SESSION->$expireskey)) {
436
                        $expires = $SESSION->$expireskey;
437
                        if ($expires > time()) {
438
                            // We have a current access token for this user
439
                            // that has not expired.
440
                            return -1;
441
                        }
442
                    }
443
                }
444
            } else {
445
                $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
446
                $authuserid = isset($SESSION->$useridkey) ? $SESSION->$useridkey : 0;
447
                if (!empty($authuserid)) {
448
                    return $authuserid;
449
                }
450
            }
451
        }
452
        return $this->curl_request('user', $this->email);
453
    }
454
 
455
    /**
456
     * Get all collections in this backpack.
457
     *
458
     * @return stdClass[] The collections.
459
     */
460
    public function get_collections() {
461
        if ($this->authenticate()) {
1441 ariadna 462
            $result = $this->curl_request('collections');
1 efrain 463
            if ($result) {
464
                return $result;
465
            }
466
        }
467
        return [];
468
    }
469
 
470
    /**
471
     * Get one collection by id.
472
     *
473
     * @param integer $collectionid
1441 ariadna 474
     * @return array The collection.
1 efrain 475
     */
476
    public function get_collection_record($collectionid) {
477
        global $DB;
478
 
1441 ariadna 479
        return $DB->get_fieldset_select('badge_external', 'entityid', 'backpackid = :bid', ['bid' => $collectionid]);
1 efrain 480
    }
481
 
482
    /**
483
     * Disconnect the backpack from this user.
484
     *
485
     * @param integer $userid The user in Moodle
486
     * @param integer $backpackid The backpack to disconnect
487
     * @return boolean
488
     */
489
    public function disconnect_backpack($userid, $backpackid) {
490
        global $DB, $USER;
491
 
492
        if (\core\session\manager::is_loggedinas() || $userid != $USER->id) {
493
            // Can't change someone elses backpack settings.
494
            return false;
495
        }
496
 
497
        $badgescache = cache::make('core', 'externalbadges');
498
 
499
        $DB->delete_records('badge_external', array('backpackid' => $backpackid));
500
        $DB->delete_records('badge_backpack', array('userid' => $userid));
501
        $badgescache->delete($userid);
502
        $this->clear_system_user_session();
503
 
504
        return true;
505
    }
506
 
507
    /**
508
     * Handle the response from getting a collection to map to an id.
509
     *
510
     * @param stdClass $data The response data.
511
     * @return string The collection id.
512
     */
513
    public function get_collection_id_from_response($data) {
1441 ariadna 514
        return $data->entityId;
1 efrain 515
    }
516
 
517
    /**
518
     * Get the last error message returned during an authentication request.
519
     *
520
     * @return string
521
     */
522
    public function get_authentication_error() {
523
        return backpack_api_mapping::get_authentication_error();
524
    }
525
 
526
    /**
1441 ariadna 527
     * List all errors occurred during the requests to the backpack.
528
     *
529
     * @return array The list of errors.
530
     */
531
    public function get_errors(): array {
532
        $errors = [];
533
        foreach ($this->mappings as $mapping) {
534
            $errors = array_merge($errors, $mapping->get_errors());
535
        }
536
 
537
        return $errors;
538
    }
539
 
540
    /**
1 efrain 541
     * Get the list of badges in a collection.
542
     *
543
     * @param stdClass $collection The collection to deal with.
544
     * @param boolean $expanded Fetch all the sub entities.
545
     * @return stdClass[]
546
     */
547
    public function get_badges($collection, $expanded = false) {
548
        global $PAGE;
549
 
550
        if ($this->authenticate()) {
1441 ariadna 551
            if (empty($collection->entityid)) {
552
                return [];
553
            }
554
            // Now we can make requests.
555
            $badges = $this->curl_request('badges', $collection->entityid);
556
            if (count($badges) == 0) {
557
                return [];
558
            }
559
            $badges = $badges[0];
560
            if ($expanded) {
561
                $publicassertions = [];
562
                $context = context_system::instance();
563
                $output = $PAGE->get_renderer('core', 'badges');
564
                foreach ($badges->assertions as $assertion) {
565
                    $remoteassertion = $this->get_assertion($assertion);
566
                    // Remote badge was fetched nested in the assertion.
567
                    $remotebadge = $remoteassertion->badgeclass;
568
                    if (!$remotebadge) {
569
                        continue;
570
                    }
571
                    $apidata = badgeclass_exporter::map_external_data($remotebadge, $this->backpackapiversion);
572
                    $exporterinstance = new badgeclass_exporter($apidata, ['context' => $context]);
573
                    $remotebadge = $exporterinstance->export($output);
574
 
575
                    $remoteissuer = $remotebadge->issuer;
576
                    $apidata = issuer_exporter::map_external_data($remoteissuer, $this->backpackapiversion);
577
                    $exporterinstance = new issuer_exporter($apidata, ['context' => $context]);
578
                    $remoteissuer = $exporterinstance->export($output);
579
 
580
                    $badgeclone = clone $remotebadge;
581
                    $badgeclone->issuer = $remoteissuer;
582
                    $remoteassertion->badge = $badgeclone;
583
                    $remotebadge->assertion = $remoteassertion;
584
                    $publicassertions[] = $remotebadge;
1 efrain 585
                }
1441 ariadna 586
                $badges = $publicassertions;
587
            }
588
            return $badges;
589
        }
590
 
591
        return [];
592
    }
593
 
594
    /**
595
     *  Get list of backpack providers for OBv2.0.
596
     *
597
     * @return string[] Array with the OBv2.0 backpack providers.
598
     */
599
    public static function get_providers(): array {
600
        $allproviders = [
601
            self::PROVIDER_CANVAS_CREDENTIALS => 'canvascredentialsprovider',
602
            self::PROVIDER_OTHER => 'otherprovider',
603
        ];
604
 
605
        foreach ($allproviders as $key => $value) {
606
            if (get_string_manager()->string_exists($value, 'badges')) {
607
                $providers[$key] = get_string($value, 'badges');
1 efrain 608
            } else {
1441 ariadna 609
                // If the string does not exist, use the key as a fallback.
610
                $providers[$key] = $value;
611
            }
612
        }
613
        return $providers;
614
    }
1 efrain 615
 
1441 ariadna 616
    /**
617
     * Get list of regions for backpack providers.
618
     *
619
     * @return array Regions with the following information: name, url and apiurl.
620
     */
621
    public static function get_regions() {
622
        global $CFG;
1 efrain 623
 
1441 ariadna 624
        $regions = [];
625
        if (empty(trim($CFG->badges_canvasregions))) {
626
            return $regions;
627
        }
628
 
629
        $entries = explode("\n", $CFG->badges_canvasregions);
630
        foreach ($entries as $entry) {
631
            if (empty(trim($entry)) || substr_count($entry, '|') != 2) {
632
                continue;
1 efrain 633
            }
1441 ariadna 634
            $entry = trim($entry);
635
            $parts = explode('|', $entry);
636
            $regions[] = [
637
                'name' => $parts[0],
638
                'url' => rtrim($parts[1], '/'),
639
                'apiurl' => rtrim($parts[2], '/'),
640
            ];
1 efrain 641
        }
1441 ariadna 642
 
643
        return $regions;
1 efrain 644
    }
1441 ariadna 645
 
646
    /**
647
     * Whether the Canvas Credentials fields should be displayed or not in the backpack form.
648
     *
649
     * @return bool True if the fields should be displayed; false otherwise.
650
     */
651
    public static function display_canvas_credentials_fields(): bool {
652
        return !empty(self::get_providers()) && !empty(self::get_regions());
653
    }
654
 
655
    /**
656
     * Get backpack URL for a given regionid.
657
     *
658
     * @param int $regionid The region identifier.
659
     * @return string|null The backpack URL.
660
     */
661
    public static function get_region_url(int $regionid): ?string {
662
        $regions = self::get_regions();
663
        if (!array_key_exists($regionid, $regions)) {
664
            return null;
665
        }
666
        return $regions[$regionid]['url'];
667
    }
668
 
669
    /**
670
     * Get backpack API URL for a given regionid.
671
     *
672
     * @param int $regionid The region identifier.
673
     * @return string|null The backpack API URL.
674
     */
675
    public static function get_region_api_url(int $regionid): ?string {
676
        $regions = self::get_regions();
677
        if (!array_key_exists($regionid, $regions)) {
678
            return null;
679
        }
680
        return $regions[$regionid]['apiurl'];
681
    }
682
 
683
    /**
684
     * Get region identifier from a given backpack URL.
685
     * When the URL is not found, the last region index is returned.
686
     *
687
     * @param string $url The backpack URL.
688
     * @return int The region identifier associated to the given backpack URL or the last region index if not found.
689
     */
690
    public static function get_regionid_from_url(string $url): int {
691
        $regions = self::get_regions();
692
        if (empty($regions)) {
693
            return self::REGION_EMPTY;
694
        }
695
 
696
        // Normalize the URL by removing the trailing slash.
697
        $normalizedurl = rtrim($url, '/');
698
        $regionurl = array_search($normalizedurl, array_column($regions, 'url'));
699
        return $regionurl !== false ? (int)$regionurl : count($regions) - 1;
700
    }
701
 
702
    /**
703
     * Check whether the given URL is a Canvas Credentials one.
704
     *
705
     * @param string $url The backpack URL.
706
     * @return bool True is the given URL is a Canvas Credentials region; false otherwise.
707
     */
708
    public static function is_canvas_credentials_region(string $url): bool {
709
        $regions = self::get_regions();
710
        return in_array($url, array_column($regions, 'url'));
711
    }
1 efrain 712
}