Skip to content

Commit 6019e21

Browse files
authored
Merge pull request 1EdTech#25 from packbackbooks/3.0.0-beta
3.0.0 beta
2 parents 58f2ab8 + 4016247 commit 6019e21

38 files changed

+1095
-371
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
.idea
44
.phpunit.result.cache
55
.php_cs.cache
6+
.php-cs-fixer.cache
67

78
build
89
composer.lock

Diff for: CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 3.0
2+
3+
* Added a new method to `ICache`: `clearAccessToken()`.
4+
* Modified the constructor arguments for `LtiServiceConnector`, `LtiAssignmentGradesService`, `LtiCourseGroupsService`, and `LtiNamesRolesProvisioningService`.
5+
16
## 2.0
27

38
* A standard naming convention was implemented for all interfaces.

Diff for: PULL_REQUEST_TEMPLATE.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@
66

77
<!-- Describe how this PR has been tested, manually and automatically. -->
88

9-
- [ ] I have run `composer test`
10-
- [ ] I have run `composer lint-fix`
9+
- [ ] I have added automated tests for my changes

Diff for: UPGRADES.md

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
## 2.0 to 3.0
2+
3+
### New method implemented on the `ICache`
4+
5+
Version 3.0 introduced changes to the `Packback\Lti1p3\Interfaces\ICache` interface, adding one methods: `clearAccessToken()`. This methods must be implemented to any custom implementations of the interface. The [Laravel Implementation Guide](https://github.com/packbackbooks/lti-1-3-php-library/wiki/Laravel-Implementation-Guide#cache) contains an example.
6+
7+
### Using GuzzleHttp\Client instead of curl
8+
9+
The `Packback\Lti1p3\LtiServiceConnector` now uses Guzzle instead of curl to make requests. This puts control of this client and it's configuration in the hands of the developer. The section below contains information on implementing this change.
10+
11+
### Changes to the LtiServiceConnector and LTI services
12+
13+
The implementation of the `Packback\Lti1p3\LtiServiceConnector` changed to act as a general API Client for the various LTI service (Assignment Grades, Names Roles Provisioning, etc.) Specifically, the constructor for the following classes now accept different arguments:
14+
15+
- `LtiAssignmentGradesService`
16+
- `LtiCourseGroupsService`
17+
- `LtiNamesRolesProvisioningService`
18+
- `LtiServiceConnector`
19+
20+
The `LtiServiceConnector` now only accepts an `ICache` and `GuzzleHttp\Client`, and does not need an `ILtiRegistration`. The [Laravel Implementation Guide](https://github.com/packbackbooks/lti-1-3-php-library/wiki/Laravel-Implementation-Guide#installation) contains an example of how to implement the service connector and configure the client.
21+
22+
The other LTI services now accept an `ILtiServiceConnector`, `ILtiRegistration`, and `$serviceData` (the registration was added as a new argument since it is no longer required for the `LtiServiceConnector`).
23+
124
## 1.0 to 2.0
225

326
### Renamed Interfaces

Diff for: composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
],
2222
"require": {
2323
"firebase/php-jwt": "^5.2",
24-
"guzzlehttp/oauth-subscriber": "^0.5.0 || ^0.6.0",
24+
"guzzlehttp/guzzle": "^7.0",
2525
"phpseclib/phpseclib": "^2.0"
2626
},
2727
"require-dev": {
28-
"matt-allan/laravel-code-style": "^0.6.0",
28+
"matt-allan/laravel-code-style": "dev-main",
2929
"mockery/mockery": "^1.4",
3030
"nesbot/carbon": "^2.43",
3131
"phpunit/phpunit": "^9.5"

Diff for: src/ImsStorage/ImsCache.php

+9
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ public function getAccessToken($key)
5656
return $this->cache[$key];
5757
}
5858

59+
public function clearAccessToken($key)
60+
{
61+
$this->loadCache();
62+
unset($this->cache[$key]);
63+
$this->saveCache();
64+
65+
return $this->cache;
66+
}
67+
5968
private function loadCache()
6069
{
6170
$cache = file_get_contents(sys_get_temp_dir().'/lti_cache.txt');

Diff for: src/Interfaces/ICache.php

+2
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ public function checkNonce($nonce);
1515
public function cacheAccessToken($key, $accessToken);
1616

1717
public function getAccessToken($key);
18+
19+
public function clearAccessToken($key);
1820
}

Diff for: src/Interfaces/ILtiServiceConnector.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,19 @@
44

55
interface ILtiServiceConnector
66
{
7-
public function getAccessToken(array $scopes);
7+
public function getAccessToken(ILtiRegistration $registration, array $scopes);
88

9-
public function makeServiceRequest(array $scopes, string $method, string $url, string $body = null, $contentType = 'application/json', $accept = 'application/json');
9+
public function makeServiceRequest(
10+
ILtiRegistration $registration,
11+
array $scopes,
12+
IServiceRequest $request,
13+
bool $shouldRetry = true
14+
): array;
15+
16+
public function getAll(
17+
ILtiRegistration $registration,
18+
array $scopes,
19+
IServiceRequest $request,
20+
string $key
21+
): array;
1022
}

Diff for: src/Interfaces/IServiceRequest.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Packback\Lti1p3\Interfaces;
4+
5+
interface IServiceRequest
6+
{
7+
public function getMethod(): string;
8+
9+
public function getUrl(): string;
10+
11+
public function getPayload(): array;
12+
13+
public function setUrl(string $url): self;
14+
15+
public function setAccessToken(string $accessToken): self;
16+
17+
public function setBody(string $body): self;
18+
19+
public function setAccept(string $accept): self;
20+
21+
public function setContentType(string $contentType): self;
22+
}

Diff for: src/LtiAbstractService.php

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Packback\Lti1p3;
4+
5+
use Packback\Lti1p3\Interfaces\ILtiRegistration;
6+
use Packback\Lti1p3\Interfaces\ILtiServiceConnector;
7+
use Packback\Lti1p3\Interfaces\IServiceRequest;
8+
9+
abstract class LtiAbstractService
10+
{
11+
private $serviceConnector;
12+
private $registration;
13+
private $serviceData;
14+
15+
public function __construct(
16+
ILtiServiceConnector $serviceConnector,
17+
ILtiRegistration $registration,
18+
array $serviceData)
19+
{
20+
$this->serviceConnector = $serviceConnector;
21+
$this->registration = $registration;
22+
$this->serviceData = $serviceData;
23+
}
24+
25+
public function getServiceData(): array
26+
{
27+
return $this->serviceData;
28+
}
29+
30+
public function setServiceData(array $serviceData): self
31+
{
32+
$this->serviceData = $serviceData;
33+
34+
return $this;
35+
}
36+
37+
abstract public function getScope(): array;
38+
39+
protected function makeServiceRequest(IServiceRequest $request): array
40+
{
41+
return $this->serviceConnector->makeServiceRequest(
42+
$this->registration,
43+
$this->getScope(),
44+
$request
45+
);
46+
}
47+
48+
protected function getAll(IServiceRequest $request, string $key): array
49+
{
50+
return $this->serviceConnector->getAll(
51+
$this->registration,
52+
$this->getScope(),
53+
$request,
54+
$key
55+
);
56+
}
57+
}

Diff for: src/LtiAssignmentsGradesService.php

+48-80
Original file line numberDiff line numberDiff line change
@@ -2,134 +2,102 @@
22

33
namespace Packback\Lti1p3;
44

5-
use Packback\Lti1p3\Interfaces\ILtiServiceConnector;
6-
7-
class LtiAssignmentsGradesService
5+
class LtiAssignmentsGradesService extends LtiAbstractService
86
{
9-
private $service_connector;
10-
private $service_data;
7+
public const CONTENTTYPE_SCORE = 'application/vnd.ims.lis.v1.score+json';
8+
public const CONTENTTYPE_LINEITEM = 'application/vnd.ims.lis.v2.lineitem+json';
9+
public const CONTENTTYPE_RESULTCONTAINER = 'application/vnd.ims.lis.v2.resultcontainer+json';
1110

12-
public function __construct(ILtiServiceConnector $service_connector, array $service_data)
11+
public function getScope(): array
1312
{
14-
$this->service_connector = $service_connector;
15-
$this->service_data = $service_data;
13+
return $this->getServiceData()['scope'];
1614
}
1715

1816
public function putGrade(LtiGrade $grade, LtiLineitem $lineitem = null)
1917
{
20-
if (!in_array(LtiConstants::AGS_SCOPE_SCORE, $this->service_data['scope'])) {
18+
if (!in_array(LtiConstants::AGS_SCOPE_SCORE, $this->getScope())) {
2119
throw new LtiException('Missing required scope', 1);
2220
}
2321
if ($lineitem !== null && empty($lineitem->getId())) {
2422
$lineitem = $this->findOrCreateLineitem($lineitem);
25-
$score_url = $lineitem->getId();
26-
} elseif ($lineitem === null && !empty($this->service_data['lineitem'])) {
27-
$score_url = $this->service_data['lineitem'];
23+
$scoreUrl = $lineitem->getId();
24+
} elseif ($lineitem === null && !empty($this->getServiceData()['lineitem'])) {
25+
$scoreUrl = $this->getServiceData()['lineitem'];
2826
} else {
2927
$lineitem = LtiLineitem::new()
3028
->setLabel('default')
3129
->setScoreMaximum(100);
3230
$lineitem = $this->findOrCreateLineitem($lineitem);
33-
$score_url = $lineitem->getId();
31+
$scoreUrl = $lineitem->getId();
3432
}
3533

3634
// Place '/scores' before url params
37-
$pos = strpos($score_url, '?');
38-
$score_url = $pos === false ? $score_url.'/scores' : substr_replace($score_url, '/scores', $pos, 0);
39-
40-
return $this->service_connector->makeServiceRequest(
41-
$this->service_data['scope'],
42-
LtiServiceConnector::METHOD_POST,
43-
$score_url,
44-
$grade,
45-
'application/vnd.ims.lis.v1.score+json'
46-
);
35+
$pos = strpos($scoreUrl, '?');
36+
$scoreUrl = $pos === false ? $scoreUrl.'/scores' : substr_replace($scoreUrl, '/scores', $pos, 0);
37+
38+
$request = new ServiceRequest(LtiServiceConnector::METHOD_POST, $scoreUrl);
39+
$request->setBody($grade);
40+
$request->setContentType(static::CONTENTTYPE_SCORE);
41+
42+
return $this->makeServiceRequest($request);
4743
}
4844

49-
public function findOrCreateLineitem(LtiLineitem $new_line_item)
45+
public function findOrCreateLineitem(LtiLineitem $newLineItem)
5046
{
51-
$line_items = $this->getLineItems();
47+
$lineitems = $this->getLineItems();
5248

53-
foreach ($line_items as $line_item) {
49+
foreach ($lineitems as $lineitem) {
5450
if (
55-
(empty($new_line_item->getResourceId()) && empty($new_line_item->getResourceLinkId())) ||
56-
(isset($line_item['resourceId']) && $line_item['resourceId'] == $new_line_item->getResourceId()) ||
57-
(isset($line_item['resourceLinkId']) && $line_item['resourceLinkId'] == $new_line_item->getResourceLinkId())
51+
(empty($newLineItem->getResourceId()) && empty($newLineItem->getResourceLinkId())) ||
52+
(isset($lineitem['resourceId']) && $lineitem['resourceId'] == $newLineItem->getResourceId()) ||
53+
(isset($lineitem['resourceLinkId']) && $lineitem['resourceLinkId'] == $newLineItem->getResourceLinkId())
5854
) {
59-
if (empty($new_line_item->getTag()) || $line_item['tag'] == $new_line_item->getTag()) {
60-
return new LtiLineitem($line_item);
55+
if (empty($newLineItem->getTag()) || $lineitem['tag'] == $newLineItem->getTag()) {
56+
return new LtiLineitem($lineitem);
6157
}
6258
}
6359
}
64-
$created_line_item = $this->service_connector->makeServiceRequest(
65-
$this->service_data['scope'],
66-
LtiServiceConnector::METHOD_POST,
67-
$this->service_data['lineitems'],
68-
$new_line_item,
69-
'application/vnd.ims.lis.v2.lineitem+json',
70-
'application/vnd.ims.lis.v2.lineitem+json'
71-
);
60+
$request = new ServiceRequest(LtiServiceConnector::METHOD_POST, $this->getServiceData()['lineitems']);
61+
$request->setBody($newLineItem)
62+
->setContentType(static::CONTENTTYPE_LINEITEM)
63+
->setAccept(static::CONTENTTYPE_LINEITEM);
64+
$createdLineItems = $this->makeServiceRequest($request);
7265

73-
return new LtiLineitem($created_line_item['body']);
66+
return new LtiLineitem($createdLineItems['body']);
7467
}
7568

7669
public function getGrades(LtiLineitem $lineitem)
7770
{
7871
$lineitem = $this->findOrCreateLineitem($lineitem);
7972
// Place '/results' before url params
8073
$pos = strpos($lineitem->getId(), '?');
81-
$results_url = $pos === false ? $lineitem->getId().'/results' : substr_replace($lineitem->getId(), '/results', $pos, 0);
82-
$scores = $this->service_connector->makeServiceRequest(
83-
$this->service_data['scope'],
84-
LtiServiceConnector::METHOD_GET,
85-
$results_url,
86-
null,
87-
null,
88-
'application/vnd.ims.lis.v2.resultcontainer+json'
89-
);
74+
$resultsUrl = $pos === false ? $lineitem->getId().'/results' : substr_replace($lineitem->getId(), '/results', $pos, 0);
75+
$request = new ServiceRequest(LtiServiceConnector::METHOD_GET, $resultsUrl);
76+
$request->setAccept();
77+
$scores = $this->makeServiceRequest($request);
9078

9179
return $scores['body'];
9280
}
9381

94-
public function getLineItems()
82+
public function getLineItems(): array
9583
{
96-
if (!in_array(LtiConstants::AGS_SCOPE_LINEITEM, $this->service_data['scope'])) {
84+
if (!in_array(LtiConstants::AGS_SCOPE_LINEITEM, $this->getScope())) {
9785
throw new LtiException('Missing required scope', 1);
9886
}
99-
$line_items = [];
100-
101-
$next_page = $this->service_data['lineitems'];
102-
103-
while ($next_page) {
104-
$page = $this->service_connector->makeServiceRequest(
105-
$this->service_data['scope'],
106-
LtiServiceConnector::METHOD_GET,
107-
$next_page,
108-
null,
109-
null,
110-
'application/vnd.ims.lti-gs.v1.contextgroupcontainer+json'
111-
);
112-
113-
$line_items = array_merge($line_items, $page['body']);
114-
$next_page = false;
115-
116-
// If the "Next" Link is not in the request headers, we can break the loop here.
117-
if (!isset($page['headers']['Link'])) {
118-
break;
119-
}
12087

121-
$link = $page['headers']['Link'];
88+
$request = new ServiceRequest(
89+
LtiServiceConnector::METHOD_GET,
90+
$this->getServiceData()['lineitems']
91+
);
92+
$request->setAccept(static::CONTENTTYPE_RESULTCONTAINER);
12293

123-
if (preg_match(LtiServiceConnector::NEXT_PAGE_REGEX, $link, $matches)) {
124-
$next_page = $matches[1];
125-
}
126-
}
94+
$lineitems = $this->getAll($request, 'lineitems');
12795

12896
// If there is only one item, then wrap it in an array so the foreach works
129-
if (isset($line_items['body']['id'])) {
130-
$line_items['body'] = [$line_items['body']];
97+
if (isset($lineitems['body']['id'])) {
98+
$lineitems['body'] = [$lineitems['body']];
13199
}
132100

133-
return $line_items;
101+
return $lineitems;
134102
}
135103
}

0 commit comments

Comments
 (0)