diff --git a/lib/lti1p3/README.md b/lib/lti1p3/README.md index 17d2e878ef7..65b09794b70 100644 --- a/lib/lti1p3/README.md +++ b/lib/lti1p3/README.md @@ -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: diff --git a/lib/lti1p3/src/LtiAbstractService.php b/lib/lti1p3/src/LtiAbstractService.php index 7ebb6daeea0..0f33d3adaf1 100644 --- a/lib/lti1p3/src/LtiAbstractService.php +++ b/lib/lti1p3/src/LtiAbstractService.php @@ -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( diff --git a/lib/lti1p3/src/LtiAssignmentsGradesService.php b/lib/lti1p3/src/LtiAssignmentsGradesService.php index 13bdcd008e2..3fffba7af0f 100644 --- a/lib/lti1p3/src/LtiAssignmentsGradesService.php +++ b/lib/lti1p3/src/LtiAssignmentsGradesService.php @@ -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, diff --git a/lib/lti1p3/src/LtiConstants.php b/lib/lti1p3/src/LtiConstants.php index cd3388962f3..73ffc08cb2f 100644 --- a/lib/lti1p3/src/LtiConstants.php +++ b/lib/lti1p3/src/LtiConstants.php @@ -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'; } diff --git a/lib/lti1p3/src/LtiDeepLink.php b/lib/lti1p3/src/LtiDeepLink.php index 4ee8d7dae69..b42a714e0a5 100644 --- a/lib/lti1p3/src/LtiDeepLink.php +++ b/lib/lti1p3/src/LtiDeepLink.php @@ -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 - */ ?> -
- - -
- - deep_link_settings['deep_link_return_url']; + + echo << + + + + +HTML; } } diff --git a/lib/lti1p3/src/LtiDeepLinkDateTimeInterval.php b/lib/lti1p3/src/LtiDeepLinkDateTimeInterval.php new file mode 100644 index 00000000000..b26c6687f69 --- /dev/null +++ b/lib/lti1p3/src/LtiDeepLinkDateTimeInterval.php @@ -0,0 +1,72 @@ +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; + } +} diff --git a/lib/lti1p3/src/LtiDeepLinkResource.php b/lib/lti1p3/src/LtiDeepLinkResource.php index 49e27b5ac8c..093f979c84f 100644 --- a/lib/lti1p3/src/LtiDeepLinkResource.php +++ b/lib/lti1p3/src/LtiDeepLinkResource.php @@ -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; } } diff --git a/lib/lti1p3/src/LtiDeepLinkResourceIframe.php b/lib/lti1p3/src/LtiDeepLinkResourceIframe.php new file mode 100644 index 00000000000..e88b6934d27 --- /dev/null +++ b/lib/lti1p3/src/LtiDeepLinkResourceIframe.php @@ -0,0 +1,58 @@ +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; + } +} diff --git a/lib/lti1p3/src/LtiDeepLinkResourceWindow.php b/lib/lti1p3/src/LtiDeepLinkResourceWindow.php new file mode 100644 index 00000000000..323d706c17d --- /dev/null +++ b/lib/lti1p3/src/LtiDeepLinkResourceWindow.php @@ -0,0 +1,92 @@ +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; + } +} diff --git a/lib/lti1p3/src/LtiMessageLaunch.php b/lib/lti1p3/src/LtiMessageLaunch.php index db92c4d6aa5..d325e375068 100644 --- a/lib/lti1p3/src/LtiMessageLaunch.php +++ b/lib/lti1p3/src/LtiMessageLaunch.php @@ -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); diff --git a/lib/lti1p3/src/LtiOidcLogin.php b/lib/lti1p3/src/LtiOidcLogin.php index 2cea3a49f61..7d12c7a51eb 100644 --- a/lib/lti1p3/src/LtiOidcLogin.php +++ b/lib/lti1p3/src/LtiOidcLogin.php @@ -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) diff --git a/lib/lti1p3/src/LtiServiceConnector.php b/lib/lti1p3/src/LtiServiceConnector.php index 79d0ba434ef..ab69986d74a 100644 --- a/lib/lti1p3/src/LtiServiceConnector.php +++ b/lib/lti1p3/src/LtiServiceConnector.php @@ -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; diff --git a/lib/lti1p3/src/ServiceRequest.php b/lib/lti1p3/src/ServiceRequest.php index 6e1a330ea7e..bd36c3e8d48 100644 --- a/lib/lti1p3/src/ServiceRequest.php +++ b/lib/lti1p3/src/ServiceRequest.php @@ -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:', diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 7c4c105cf8a..578af458125 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -436,7 +436,7 @@ All rights reserved. lti1p3 LTI 1.3 Tool Library A library used for building IMS-certified LTI 1.3 tool providers in PHP. - 5.2.6 + 5.4.1 Apache 2.0 https://github.com/packbackbooks/lti-1-3-php-library