Merge branch 'MDL-78258-master' of https://github.com/snake/moodle

This commit is contained in:
Jun Pataleta 2023-09-08 10:06:01 +08:00
commit f32f669f53
No known key found for this signature in database
GPG Key ID: F83510526D99E2C7
14 changed files with 389 additions and 63 deletions

View File

@ -4,7 +4,7 @@ A library used for building IMS-certified LTI 1.3 tool providers in PHP.
This library is a fork of the [packbackbooks/lti-1-3-php-library](https://github.com/packbackbooks/lti-1-3-php-library), patched specifically for use in [Moodle](https://github.com/moodle/moodle).
It is currently based on version [5.2.6 of the packbackbooks/lti-1-3-php-library](https://github.com/packbackbooks/lti-1-3-php-library/releases/tag/v5.2.6) library.
It is currently based on version [5.4.1 of the packbackbooks/lti-1-3-php-library](https://github.com/packbackbooks/lti-1-3-php-library/releases/tag/v5.4.1) library.
The following changes are included so that the library may be used with Moodle:

View File

@ -36,6 +36,13 @@ abstract class LtiAbstractService
abstract public function getScope(): array;
protected function validateScopes(array $scopes): void
{
if (empty(array_intersect($scopes, $this->getScope()))) {
throw new LtiException('Missing required scope', 1);
}
}
protected function makeServiceRequest(IServiceRequest $request): array
{
return $this->serviceConnector->makeServiceRequest(

View File

@ -30,9 +30,7 @@ class LtiAssignmentsGradesService extends LtiAbstractService
public function putGrade(LtiGrade $grade, LtiLineitem $lineitem = null)
{
if (!in_array(LtiConstants::AGS_SCOPE_SCORE, $this->getScope())) {
throw new LtiException('Missing required scope', 1);
}
$this->validateScopes([LtiConstants::AGS_SCOPE_SCORE]);
$lineitem = $this->ensureLineItemExists($lineitem);
@ -70,7 +68,7 @@ class LtiAssignmentsGradesService extends LtiAbstractService
{
$request = new ServiceRequest(
ServiceRequest::METHOD_PUT,
$this->getServiceData()['lineitems'],
$this->getServiceData()['lineitem'],
ServiceRequest::TYPE_UPDATE_LINEITEM
);
@ -98,6 +96,17 @@ class LtiAssignmentsGradesService extends LtiAbstractService
return new LtiLineitem($createdLineItem['body']);
}
public function deleteLineitem(): array
{
$request = new ServiceRequest(
ServiceRequest::METHOD_DELETE,
$this->getServiceData()['lineitem'],
ServiceRequest::TYPE_DELETE_LINEITEM
);
return $this->makeServiceRequest($request);
}
public function findOrCreateLineitem(LtiLineitem $newLineItem): LtiLineitem
{
return $this->findLineItem($newLineItem) ?? $this->createLineitem($newLineItem);
@ -118,16 +127,13 @@ class LtiAssignmentsGradesService extends LtiAbstractService
ServiceRequest::TYPE_GET_GRADES
);
$request->setAccept(static::CONTENTTYPE_RESULTCONTAINER);
$scores = $this->makeServiceRequest($request);
return $scores['body'];
return $this->getAll($request);
}
public function getLineItems(): array
{
if (!in_array(LtiConstants::AGS_SCOPE_LINEITEM, $this->getScope())) {
throw new LtiException('Missing required scope', 1);
}
$this->validateScopes([LtiConstants::AGS_SCOPE_LINEITEM, LtiConstants::AGS_SCOPE_LINEITEM_READONLY]);
$request = new ServiceRequest(
ServiceRequest::METHOD_GET,
@ -148,9 +154,7 @@ class LtiAssignmentsGradesService extends LtiAbstractService
public function getLineItem(string $url): LtiLineitem
{
if (!in_array(LtiConstants::AGS_SCOPE_LINEITEM, $this->getScope())) {
throw new LtiException('Missing required scope', 1);
}
$this->validateScopes([LtiConstants::AGS_SCOPE_LINEITEM, LtiConstants::AGS_SCOPE_LINEITEM_READONLY]);
$request = new ServiceRequest(
ServiceRequest::METHOD_GET,

View File

@ -27,6 +27,7 @@ class LtiConstants
public const DL_CONTENT_ITEMS = 'https://purl.imsglobal.org/spec/lti-dl/claim/content_items';
public const DL_DATA = 'https://purl.imsglobal.org/spec/lti-dl/claim/data';
public const DL_DEEP_LINK_SETTINGS = 'https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings';
public const DL_RESOURCE_LINK_TYPE = 'ltiResourceLink';
// LTI NRPS
public const NRPS_CLAIM_SERVICE = 'https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice';
@ -72,6 +73,7 @@ class LtiConstants
public const MEMBERSHIP_MANAGER = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Manager';
public const MEMBERSHIP_MEMBER = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Member';
public const MEMBERSHIP_OFFICER = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Officer';
// Context sub-roles
public const MEMBERSHIP_EXTERNALINSTRUCTOR = 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#ExternalInstructor';
public const MEMBERSHIP_GRADER = 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Grader';
@ -94,6 +96,7 @@ class LtiConstants
// Message Types
public const MESSAGE_TYPE_DEEPLINK = 'LtiDeepLinkingRequest';
public const MESSAGE_TYPE_DEEPLINK_RESPONSE = 'LtiDeepLinkingResponse';
public const MESSAGE_TYPE_RESOURCE = 'LtiResourceLinkRequest';
public const MESSAGE_TYPE_SUBMISSIONREVIEW = 'LtiSubmissionReviewRequest';
}

View File

@ -27,7 +27,7 @@ class LtiDeepLink
'iat' => time(),
'nonce' => LtiOidcLogin::secureRandomString('nonce-'),
LtiConstants::DEPLOYMENT_ID => $this->deployment_id,
LtiConstants::MESSAGE_TYPE => 'LtiDeepLinkingResponse',
LtiConstants::MESSAGE_TYPE => LtiConstants::MESSAGE_TYPE_DEEPLINK_RESPONSE,
LtiConstants::VERSION => LtiConstants::V1_3,
LtiConstants::DL_CONTENT_ITEMS => array_map(function ($resource) {
return $resource->toArray();
@ -43,19 +43,27 @@ class LtiDeepLink
return JWT::encode($message_jwt, $this->registration->getToolPrivateKey(), 'RS256', $this->registration->getKid());
}
/**
* This method builds an auto-submitting HTML form to post the deep linking response message
* back to platform, as per LTI-DL 2.0 specification. The resulting HTML is then written to standard output,
* so calling this method will automatically send an HTTP response to conclude the content selection flow.
*
* @param LtiDeepLinkResource[] $resources The list of selected resources to be sent to the platform
*
* @todo Consider wrapping the content inside a well-formed HTML document,
* and returning it instead of directly writing to standard output
*/
public function outputResponseForm($resources)
{
$jwt = $this->getResponseJwt($resources);
/*
* @todo Fix this
*/ ?>
<form id="auto_submit" action="<?php echo $this->deep_link_settings['deep_link_return_url']; ?>" method="POST">
<input type="hidden" name="JWT" value="<?php echo $jwt; ?>" />
<input type="submit" name="Go" />
</form>
<script>
document.getElementById('auto_submit').submit();
</script>
<?php
$formActionUrl = $this->deep_link_settings['deep_link_return_url'];
echo <<<HTML
<form id="auto_submit" action="{$formActionUrl}" method="POST">
<input type="hidden" name="JWT" value="{$jwt}" />
<input type="submit" name="Go" />
</form>
<script>document.getElementById('auto_submit').submit();</script>
HTML;
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace Packback\Lti1p3;
use DateTime;
class LtiDeepLinkDateTimeInterval
{
private ?DateTime $start;
private ?DateTime $end;
public function __construct(DateTime $start = null, DateTime $end = null)
{
if ($start !== null && $end !== null && $end < $start) {
throw new LtiException('Interval start time cannot be greater than end time');
}
$this->start = $start ?? null;
$this->end = $end ?? null;
}
public static function new(): LtiDeepLinkDateTimeInterval
{
return new LtiDeepLinkDateTimeInterval();
}
public function setStart(?DateTime $start): LtiDeepLinkDateTimeInterval
{
$this->start = $start;
return $this;
}
public function getStart(): ?DateTime
{
return $this->start;
}
public function setEnd(?DateTime $end): LtiDeepLinkDateTimeInterval
{
$this->end = $end;
return $this;
}
public function getEnd(): ?DateTime
{
return $this->end;
}
public function toArray(): array
{
if (!isset($this->start) && !isset($this->end)) {
throw new LtiException('At least one of the interval bounds must be specified on the object instance');
}
if ($this->start !== null && $this->end !== null && $this->end < $this->start) {
throw new LtiException('Interval start time cannot be greater than end time');
}
$dateTimeInterval = [];
if (isset($this->start)) {
$dateTimeInterval['startDateTime'] = $this->start->format(DateTime::ATOM);
}
if (isset($this->end)) {
$dateTimeInterval['endDateTime'] = $this->end->format(DateTime::ATOM);
}
return $dateTimeInterval;
}
}

View File

@ -4,7 +4,7 @@ namespace Packback\Lti1p3;
class LtiDeepLinkResource
{
private $type = 'ltiResourceLink';
private $type = LtiConstants::DL_RESOURCE_LINK_TYPE;
private $title;
private $text;
private $url;
@ -13,6 +13,10 @@ class LtiDeepLinkResource
private $thumbnail;
private $custom_params = [];
private $target = 'iframe';
private $iframe;
private $window;
private $availability_interval;
private $submission_interval;
public static function new(): LtiDeepLinkResource
{
@ -115,11 +119,19 @@ class LtiDeepLinkResource
return $this;
}
/**
* @deprecated This field maps the "presentation" resource property, which is non-standard.
* Consider using "iframe" and/or "window" instead.
*/
public function getTarget(): string
{
return $this->target;
}
/**
* @deprecated This field maps the "presentation" resource property, which is non-standard.
* Consider using "iframe" and/or "window" instead.
*/
public function setTarget(string $value): LtiDeepLinkResource
{
$this->target = $value;
@ -127,17 +139,69 @@ class LtiDeepLinkResource
return $this;
}
public function getIframe(): ?LtiDeepLinkResourceIframe
{
return $this->iframe;
}
public function setIframe(?LtiDeepLinkResourceIframe $iframe): LtiDeepLinkResource
{
$this->iframe = $iframe;
return $this;
}
public function getWindow(): ?LtiDeepLinkResourceWindow
{
return $this->window;
}
public function setWindow(?LtiDeepLinkResourceWindow $window): LtiDeepLinkResource
{
$this->window = $window;
return $this;
}
public function getAvailabilityInterval(): ?LtiDeepLinkDateTimeInterval
{
return $this->availability_interval;
}
public function setAvailabilityInterval(?LtiDeepLinkDateTimeInterval $availabilityInterval): LtiDeepLinkResource
{
$this->availability_interval = $availabilityInterval;
return $this;
}
public function getSubmissionInterval(): ?LtiDeepLinkDateTimeInterval
{
return $this->submission_interval;
}
public function setSubmissionInterval(?LtiDeepLinkDateTimeInterval $submissionInterval): LtiDeepLinkResource
{
$this->submission_interval = $submissionInterval;
return $this;
}
public function toArray(): array
{
$resource = [
'type' => $this->type,
'title' => $this->title,
'text' => $this->text,
'url' => $this->url,
'presentation' => [
'documentTarget' => $this->target,
],
];
if (isset($this->title)) {
$resource['title'] = $this->title;
}
if (isset($this->text)) {
$resource['text'] = $this->text;
}
if (isset($this->url)) {
$resource['url'] = $this->url;
}
if (!empty($this->custom_params)) {
$resource['custom'] = $this->custom_params;
}
@ -154,6 +218,26 @@ class LtiDeepLinkResource
];
}
// Kept for backwards compatibility
if (!isset($this->iframe) && !isset($this->window)) {
$resource['presentation'] = [
'documentTarget' => $this->target,
];
}
if (isset($this->iframe)) {
$resource['iframe'] = $this->iframe->toArray();
}
if (isset($this->window)) {
$resource['window'] = $this->window->toArray();
}
if (isset($this->availability_interval)) {
$resource['available'] = $this->availability_interval->toArray();
}
if (isset($this->submission_interval)) {
$resource['submission'] = $this->submission_interval->toArray();
}
return $resource;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Packback\Lti1p3;
class LtiDeepLinkResourceIframe
{
private ?int $width;
private ?int $height;
public function __construct(int $width = null, int $height = null)
{
$this->width = $width ?? null;
$this->height = $height ?? null;
}
public static function new(): LtiDeepLinkResourceIframe
{
return new LtiDeepLinkResourceIframe();
}
public function setWidth(?int $width): LtiDeepLinkResourceIframe
{
$this->width = $width;
return $this;
}
public function getWidth(): ?int
{
return $this->width;
}
public function setHeight(?int $height): LtiDeepLinkResourceIframe
{
$this->height = $height;
return $this;
}
public function getHeight(): ?int
{
return $this->height;
}
public function toArray(): array
{
$iframe = [];
if (isset($this->width)) {
$iframe['width'] = $this->width;
}
if (isset($this->height)) {
$iframe['height'] = $this->height;
}
return $iframe;
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace Packback\Lti1p3;
class LtiDeepLinkResourceWindow
{
private ?string $target_name;
private ?int $width;
private ?int $height;
private ?string $window_features;
public function __construct(string $targetName = null, int $width = null, int $height = null, string $windowFeatures = null)
{
$this->target_name = $targetName ?? null;
$this->width = $width ?? null;
$this->height = $height ?? null;
$this->window_features = $windowFeatures ?? null;
}
public static function new(): LtiDeepLinkResourceWindow
{
return new LtiDeepLinkResourceWindow();
}
public function setTargetName(?string $targetName): LtiDeepLinkResourceWindow
{
$this->target_name = $targetName;
return $this;
}
public function getTargetName(): ?string
{
return $this->target_name;
}
public function setWidth(?int $width): LtiDeepLinkResourceWindow
{
$this->width = $width;
return $this;
}
public function getWidth(): ?int
{
return $this->width;
}
public function setHeight(?int $height): LtiDeepLinkResourceWindow
{
$this->height = $height;
return $this;
}
public function getHeight(): ?int
{
return $this->height;
}
public function setWindowFeatures(?string $windowFeatures): LtiDeepLinkResourceWindow
{
$this->window_features = $windowFeatures;
return $this;
}
public function getWindowFeatures(): ?string
{
return $this->window_features;
}
public function toArray(): array
{
$window = [];
if (isset($this->target_name)) {
$window['targetName'] = $this->target_name;
}
if (isset($this->width)) {
$window['width'] = $this->width;
}
if (isset($this->height)) {
$window['height'] = $this->height;
}
if (isset($this->window_features)) {
$window['windowFeatures'] = $this->window_features;
}
return $window;
}
}

View File

@ -6,7 +6,6 @@ use Exception;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\TransferException;
use Packback\Lti1p3\Interfaces\ICache;
use Packback\Lti1p3\Interfaces\ICookie;
@ -21,9 +20,9 @@ class LtiMessageLaunch
public const TYPE_DEEPLINK = 'LtiDeepLinkingRequest';
public const TYPE_SUBMISSIONREVIEW = 'LtiSubmissionReviewRequest';
public const TYPE_RESOURCELINK = 'LtiResourceLinkRequest';
public const ERR_FETCH_PUBLIC_KEY = 'Failed to fetch public key.';
public const ERR_NO_PUBLIC_KEY = 'Unable to find public key.';
public const ERR_NO_MATCHING_PUBLIC_KEY = 'Unable to find a public key which matches your JWT.';
public const ERR_STATE_NOT_FOUND = 'Please make sure you have cookies enabled in this browser and that you are not in private or incognito mode';
public const ERR_MISSING_ID_TOKEN = 'Missing id_token.';
public const ERR_INVALID_ID_TOKEN = 'Invalid id_token, JWT must contain 3 parts';
@ -36,7 +35,6 @@ class LtiMessageLaunch
* error message is built.
*/
public const ERR_MISSING_REGISTRATION = 'LTI 1.3 Registration not found for Issuer :issuerUrl and Client ID :clientId. Please make sure the LMS has provided the right information, and that the LMS has been registered correctly in the tool.';
public const ERR_CLIENT_NOT_REGISTERED = 'Client id not registered for this issuer.';
public const ERR_NO_KID = 'No KID specified in the JWT Header.';
public const ERR_INVALID_SIGNATURE = 'Invalid signature on id_token';
@ -47,7 +45,6 @@ class LtiMessageLaunch
public const ERR_INVALID_MESSAGE = 'Message validation failed.';
public const ERR_INVALID_ALG = 'Invalid alg was specified in the JWT header.';
public const ERR_MISMATCHED_ALG_KEY = 'The alg specified in the JWT header is incompatible with the JWK key type.';
private $db;
private $cache;
private $cookie;
@ -70,10 +67,10 @@ class LtiMessageLaunch
/**
* Constructor.
*
* @param IDatabase $database Instance of the database interface used for looking up registrations and deployments
* @param ICache $cache Instance of the Cache interface used to loading and storing launches
* @param ICookie $cookie Instance of the Cookie interface used to set and read cookies
* @param ILtiServiceConnector $serviceConnector Instance of the LtiServiceConnector used to by LTI services to make API requests
* @param IDatabase $database Instance of the database interface used for looking up registrations and deployments
* @param ICache $cache Instance of the Cache interface used to loading and storing launches
* @param ICookie $cookie Instance of the Cookie interface used to set and read cookies
* @param ILtiServiceConnector $serviceConnector Instance of the LtiServiceConnector used to by LTI services to make API requests
*/
public function __construct(
IDatabase $database,
@ -105,13 +102,12 @@ class LtiMessageLaunch
/**
* Load an LtiMessageLaunch from a Cache using a launch id.
*
* @param string $launch_id The launch id of the LtiMessageLaunch object that is being pulled from the cache
* @param IDatabase $database Instance of the database interface used for looking up registrations and deployments
* @param ICache $cache Instance of the Cache interface used to loading and storing launches. If non is provided launch data will be store in $_SESSION.
* @param string $launch_id The launch id of the LtiMessageLaunch object that is being pulled from the cache
* @param IDatabase $database Instance of the database interface used for looking up registrations and deployments
* @param ICache $cache Instance of the Cache interface used to loading and storing launches. If non is provided launch data will be store in $_SESSION.
* @return LtiMessageLaunch A populated and validated LtiMessageLaunch
*
* @throws LtiException Will throw an LtiException if validation fails or launch cannot be found
*
* @return LtiMessageLaunch A populated and validated LtiMessageLaunch
*/
public static function fromCache(
$launch_id,
@ -129,11 +125,10 @@ class LtiMessageLaunch
/**
* Validates all aspects of an incoming LTI message launch and caches the launch if successful.
*
* @param array|string $request An array of post request parameters. If not set will default to $_POST.
* @param array|string $request An array of post request parameters. If not set will default to $_POST.
* @return LtiMessageLaunch Will return $this if validation is successful
*
* @throws LtiException Will throw an LtiException if validation fails
*
* @return LtiMessageLaunch Will return $this if validation is successful
*/
public function validate(array $request = null)
{
@ -288,7 +283,7 @@ class LtiMessageLaunch
return $this->launch_id;
}
public static function getMissingRegistrationErrorMsg(string $issuerUrl, ?string $clientId = null): string
public static function getMissingRegistrationErrorMsg(string $issuerUrl, string $clientId = null): string
{
// Guard against client ID being null
if (!isset($clientId)) {
@ -342,7 +337,7 @@ class LtiMessageLaunch
}
// Could not find public key with a matching kid and alg.
throw new LtiException(static::ERR_NO_PUBLIC_KEY);
throw new LtiException(static::ERR_NO_MATCHING_PUBLIC_KEY);
}
/**
@ -456,7 +451,8 @@ class LtiMessageLaunch
// Validate JWT signature
try {
JWT::decode($this->request['id_token'], $public_key, ['RS256']);
$headers = new \stdClass();
JWT::decode($this->request['id_token'], $public_key, $headers);
} catch (ExpiredException $e) {
// Error validating signature.
throw new LtiException(static::ERR_INVALID_SIGNATURE);

View File

@ -9,11 +9,9 @@ use Packback\Lti1p3\Interfaces\IDatabase;
class LtiOidcLogin
{
public const COOKIE_PREFIX = 'lti1p3_';
public const ERROR_MSG_LAUNCH_URL = 'No launch URL configured';
public const ERROR_MSG_ISSUER = 'Could not find issuer';
public const ERROR_MSG_LOGIN_HINT = 'Could not find login hint';
private $db;
private $cache;
private $cookie;
@ -21,9 +19,9 @@ class LtiOidcLogin
/**
* Constructor.
*
* @param IDatabase $database Instance of the Database interface used for looking up registrations and deployments
* @param ICache $cache instance of the Cache interface used to loading and storing launches
* @param ICookie $cookie instance of the Cookie interface used to set and read cookies
* @param IDatabase $database Instance of the Database interface used for looking up registrations and deployments
* @param ICache $cache instance of the Cache interface used to loading and storing launches
* @param ICookie $cookie instance of the Cookie interface used to set and read cookies
*/
public function __construct(IDatabase $database, ICache $cache = null, ICookie $cookie = null)
{
@ -43,9 +41,8 @@ class LtiOidcLogin
/**
* Calculate the redirect location to return to based on an OIDC third party initiated login request.
*
* @param string $launch_url URL to redirect back to after the OIDC login. This URL must match exactly a URL white listed in the platform.
* @param array|string $request An array of request parameters. If not set will default to $_REQUEST.
*
* @param string $launch_url URL to redirect back to after the OIDC login. This URL must match exactly a URL white listed in the platform.
* @param array|string $request An array of request parameters. If not set will default to $_REQUEST.
* @return Redirect returns a redirect object containing the fully formed OIDC login URL
*/
public function doOidcLoginRedirect($launch_url, array $request = null)

View File

@ -15,7 +15,6 @@ use Packback\Lti1p3\Interfaces\IServiceRequest;
class LtiServiceConnector implements ILtiServiceConnector
{
public const NEXT_PAGE_REGEX = '/<([^>]*)>; ?rel="next"/i';
private $cache;
private $client;
private $debuggingMode = false;
@ -213,7 +212,7 @@ class LtiServiceConnector implements ILtiServiceConnector
private function getNextUrl(array $headers)
{
$subject = $headers['Link'] ?? '';
$subject = $headers['Link'] ?? $headers['link'] ?? '';
preg_match(static::NEXT_PAGE_REGEX, $subject, $matches);
return $matches[1] ?? null;

View File

@ -7,6 +7,7 @@ use Packback\Lti1p3\Interfaces\IServiceRequest;
class ServiceRequest implements IServiceRequest
{
// Request methods
public const METHOD_DELETE = 'DELETE';
public const METHOD_GET = 'GET';
public const METHOD_POST = 'POST';
public const METHOD_PUT = 'PUT';
@ -14,21 +15,25 @@ class ServiceRequest implements IServiceRequest
// Request types
public const TYPE_UNSUPPORTED = 'unsupported';
public const TYPE_AUTH = 'auth';
// MessageLaunch
public const TYPE_GET_KEYSET = 'get_keyset';
// AGS
public const TYPE_GET_GRADES = 'get_grades';
public const TYPE_SYNC_GRADE = 'sync_grades';
public const TYPE_CREATE_LINEITEM = 'create_lineitem';
public const TYPE_DELETE_LINEITEM = 'delete_lineitem';
public const TYPE_GET_LINEITEMS = 'get_lineitems';
public const TYPE_GET_LINEITEM = 'get_lineitem';
public const TYPE_UPDATE_LINEITEM = 'update_lineitem';
// CGS
public const TYPE_GET_GROUPS = 'get_groups';
public const TYPE_GET_SETS = 'get_sets';
// NRPS
public const TYPE_GET_MEMBERSHIPS = 'get_memberships';
private $method;
private $url;
private $type;
@ -125,6 +130,7 @@ class ServiceRequest implements IServiceRequest
static::TYPE_GET_GRADES => 'Getting grades:',
static::TYPE_SYNC_GRADE => 'Syncing grade for this lti_user_id:',
static::TYPE_CREATE_LINEITEM => 'Creating lineitem:',
static::TYPE_DELETE_LINEITEM => 'Deleting lineitem:',
static::TYPE_GET_LINEITEMS => 'Getting lineitems:',
static::TYPE_GET_LINEITEM => 'Getting a lineitem:',
static::TYPE_UPDATE_LINEITEM => 'Updating lineitem:',

View File

@ -436,7 +436,7 @@ All rights reserved.</copyright>
<location>lti1p3</location>
<name>LTI 1.3 Tool Library</name>
<description>A library used for building IMS-certified LTI 1.3 tool providers in PHP.</description>
<version>5.2.6</version>
<version>5.4.1</version>
<license>Apache</license>
<licenseversion>2.0</licenseversion>
<repository>https://github.com/packbackbooks/lti-1-3-php-library</repository>