77 'basic-lti-launch-request',
78 'ConfigureLaunchRequest',
80 'ContentItemSelectionRequest',
81 'ContentItemUpdateRequest',
82 'ToolProxyRegistrationRequest',
90 private static array
$LTI_CONSUMER_SETTING_NAMES = array(
'custom_tc_profile_url',
'custom_system_setting_url',
'custom_oauth2_access_token_url');
96 'ext_ims_lis_memberships_id',
'ext_ims_lis_memberships_url',
97 'custom_context_memberships_url',
'custom_context_memberships_v2_url',
98 'custom_context_group_sets_url',
'custom_context_groups_url',
99 'custom_lineitems_url',
'custom_ags_scopes' 106 'ext_ims_lis_basic_outcome_url',
'ext_ims_lis_resultvalue_sourcedids',
'ext_outcome_data_values_accepted',
107 'ext_ims_lis_memberships_id',
'ext_ims_lis_memberships_url',
108 'ext_ims_lti_tool_setting',
'ext_ims_lti_tool_setting_id',
'ext_ims_lti_tool_setting_url',
109 'custom_link_setting_url',
'custom_link_memberships_url',
110 'custom_lineitems_url',
'custom_lineitem_url',
'custom_ags_scopes',
123 'User.image' =>
'user_image',
124 'User.username' =>
'username',
125 'User.scope.mentor' =>
'role_scope_mentor',
126 'Membership.role' =>
'roles',
127 'Person.sourcedId' =>
'lis_person_sourcedid',
128 'Person.name.full' =>
'lis_person_name_full',
129 'Person.name.family' =>
'lis_person_name_family',
130 'Person.name.given' =>
'lis_person_name_given',
131 'Person.email.primary' =>
'lis_person_contact_email_primary',
132 'Context.id' =>
'context_id',
133 'Context.type' =>
'context_type',
134 'Context.title' =>
'context_title',
135 'Context.label' =>
'context_label',
136 'CourseOffering.sourcedId' =>
'lis_course_offering_sourcedid',
137 'CourseSection.sourcedId' =>
'lis_course_section_sourcedid',
138 'CourseSection.label' =>
'context_label',
139 'CourseSection.title' =>
'context_title',
140 'ResourceLink.id' =>
'resource_link_id',
141 'ResourceLink.title' =>
'resource_link_title',
142 'ResourceLink.description' =>
'resource_link_description',
143 'Result.sourcedId' =>
'lis_result_sourcedid',
144 'BasicOutcome.url' =>
'lis_outcome_service_url',
145 'ToolConsumerProfile.url' =>
'custom_tc_profile_url',
146 'ToolProxy.url' =>
'tool_proxy_url',
147 'ToolProxy.custom.url' =>
'custom_system_setting_url',
148 'ToolProxyBinding.custom.url' =>
'custom_context_setting_url',
149 'LtiLink.custom.url' =>
'custom_link_setting_url',
150 'LineItems.url' =>
'custom_lineitems_url',
151 'LineItem.url' =>
'custom_lineitem_url',
152 'ToolProxyBinding.memberships.url' =>
'custom_context_memberships_url',
153 'ToolProxyBinding.nrps.url' =>
'custom_context_memberships_v2_url',
154 'LtiLink.memberships.url' =>
'custom_link_memberships_url',
155 'LtiLink.acs.url' =>
'custom_ap_acs_url' 386 $this->secret = null;
387 $this->messageUrl = null;
388 $this->initiateLoginUrl = null;
389 $this->redirectionUris = null;
390 $this->rsaKey = null;
391 $this->signatureMethod =
'HMAC-SHA1';
392 $this->encryptionMethod = null;
393 $this->ltiVersion = null;
395 $this->enabled =
false;
396 $this->enableFrom = null;
397 $this->enableUntil = null;
398 $this->lastAccess = null;
399 $this->created = null;
400 $this->updated = null;
401 $this->constraints = array();
403 $this->vendor = new \ILIAS\LTI\ToolProvider\Content\Item(null);
405 $this->product = new \ILIAS\LTI\ToolProvider\Content\Item(null);
406 $this->requiredServices = array();
407 $this->optionalServices = array();
408 $this->resourceHandlers = array();
418 return $this->dataConnector->saveTool($this);
426 public function delete():
bool 428 return $this->dataConnector->deleteTool($this);
438 if (is_null($this->messageParameters)) {
442 $this->debugMode = (isset($this->messageParameters[
'custom_debug']) &&
443 (strtolower($this->messageParameters[
'custom_debug']) ===
'true'));
444 if ($this->debugMode) {
449 if (!empty($this->messageParameters[
'lti_message_type']) &&
450 (($this->messageParameters[
'lti_message_type'] ===
'ContentItemSelectionRequest') || ($this->messageParameters[
'lti_message_type'] ===
'ContentItemUpdateRequest')) &&
451 !empty($this->messageParameters[
'content_item_return_url'])) {
452 $this->returnUrl = $this->messageParameters[
'content_item_return_url'];
454 if (empty($this->returnUrl) && !empty($this->messageParameters[
'launch_presentation_return_url'])) {
455 $this->returnUrl = $this->messageParameters[
'launch_presentation_return_url'];
469 if ($this->debugMode) {
472 if (
$_SERVER[
'REQUEST_METHOD'] ===
'HEAD') {
474 } elseif (isset($parameters[
'iss']) && (strlen($parameters[
'iss']) > 0)) {
476 if (!isset($parameters[
'login_hint']) || (strlen($parameters[
'login_hint']) <= 0)) {
478 $this->reason =
'Missing login_hint parameter';
479 } elseif (!isset($parameters[
'target_link_uri']) || (strlen($parameters[
'target_link_uri']) <= 0)) {
481 $this->reason =
'Missing target_link_uri parameter';
485 } elseif (isset($parameters[
'openid_configuration']) && (strlen($parameters[
'openid_configuration']) > 0)) {
492 if (empty($this->output)) {
494 if ($this->ok && ($this->messageParameters[
'lti_message_type'] ===
'ToolProxyRegistrationRequest')) {
495 $this->platform->save();
501 $errorMessage =
"Request failed with reason: '{$this->reason}'";
502 if (!empty($this->details)) {
503 $errorMessage .= PHP_EOL .
'Debug information:';
504 foreach ($this->details as $detail) {
505 $errorMessage .= PHP_EOL .
" {$detail}";
525 $this->constraints[
$name] = array(
'required' => $required,
'max_length' => $maxLength,
'messages' => $messageTypes);
553 return $this->dataConnector->getPlatforms();
565 $services = $this->platform->profile->service_offered;
566 if (is_array($services)) {
570 if (!is_array($service->format) || !in_array($format, $service->format)) {
574 foreach ($methods as $method) {
575 if (!is_array($service->action) || !in_array($method, $service->action)) {
576 $missing[] = $method;
580 if (count($methods) <= 0) {
632 ### PROTECTED METHODS 640 $this->reason =
'No onLaunch method found for tool';
649 $this->reason =
'No onConfigure method found for tool';
658 $this->reason =
'No onDashboard method found for tool';
667 $this->reason =
'No onContentItem method found for tool';
676 $this->reason =
'No onContentItemUpdate method found for tool';
685 $this->reason =
'No onRegister method found for tool';
697 $registrationConfig = $this->
sendRegistration($platformConfig, $toolConfig);
711 $this->reason =
'No onLtiStartProctoring method found for tool';
720 $this->reason =
'No onLtiEndAssessment method found for tool';
751 $this->ok = !empty($parameters[
'openid_configuration']);
753 $http =
new HttpMessage($parameters[
'openid_configuration']);
754 $this->ok =
$http->send();
756 $platformConfig = json_decode(
$http->response,
true);
757 $this->ok = !empty($platformConfig);
760 $this->reason =
'Unable to access platform configuration details.';
763 $this->reason =
'Invalid registration request: missing openid_configuration parameter.';
766 $this->ok = !empty($platformConfig[
'registration_endpoint']) && !empty($platformConfig[
'jwks_uri']) && !empty($platformConfig[
'authorization_endpoint']) &&
767 !empty($platformConfig[
'token_endpoint']) && !empty($platformConfig[
'https://purl.imsglobal.org/spec/lti-platform-configuration']) &&
768 !empty($platformConfig[
'claims_supported']) && !empty($platformConfig[
'scopes_supported']) &&
769 !empty($platformConfig[
'id_token_signing_alg_values_supported']) &&
770 !empty($platformConfig[
'https://purl.imsglobal.org/spec/lti-platform-configuration'][
'product_family_code']) &&
771 !empty($platformConfig[
'https://purl.imsglobal.org/spec/lti-platform-configuration'][
'version']) &&
772 !empty($platformConfig[
'https://purl.imsglobal.org/spec/lti-platform-configuration'][
'messages_supported']);
774 $this->reason =
'Invalid platform configuration details.';
780 $algorithms = \array_intersect(
781 $jwtClient::getSupportedAlgorithms(),
782 $platformConfig[
'id_token_signing_alg_values_supported']
784 $this->ok = !empty($algorithms);
786 rsort($platformConfig[
'id_token_signing_alg_values_supported']);
788 $this->reason =
'None of the signature algorithms offered by the platform is supported.';
793 $platformConfig = null;
796 return $platformConfig;
806 $claimsMapping = array(
808 'Person.name.full' =>
'name',
809 'Person.name.given' =>
'given_name',
810 'Person.name.family' =>
'family_name',
811 'Person.email.primary' =>
'email' 813 $toolName = (!empty($this->product->name)) ? $this->product->name :
'Unnamed tool';
814 $toolDescription = (!empty($this->product->description)) ? $this->product->description :
'';
816 $oauthRequest = LTIOAuth\OAuthRequest::from_request();
817 $toolUrl = $oauthRequest->get_normalized_http_url();
818 $pos = strpos($toolUrl,
'//');
819 $domain = substr($toolUrl, $pos + 2);
820 $domain = substr($domain, 0, strpos($domain,
'/'));
821 $claimsSupported = $platformConfig[
'claims_supported'];
822 $messagesSupported = $platformConfig[
'https://purl.imsglobal.org/spec/lti-platform-configuration'][
'messages_supported'];
823 $scopesSupported = $platformConfig[
'scopes_supported'];
827 $variables = array();
828 $constants = array();
829 $redirectUris = array();
830 foreach ($this->resourceHandlers as $resourceHandler) {
831 if (empty($iconUrl)) {
832 $iconUrl = $resourceHandler->icon;
834 foreach (array_merge($resourceHandler->optionalMessages, $resourceHandler->requiredMessages) as $message) {
835 $type = $message->type;
839 $capabilities = array();
840 if (
$type ===
'LtiResourceLinkRequest') {
841 $toolUrl =
"{$this->baseUrl}{$message->path}";
842 $redirectUris[] = $toolUrl;
843 $capabilities = $message->capabilities;
844 $variables = array_merge($variables, $message->variables);
845 $constants = array_merge($constants, $message->constants);
846 } elseif (in_array(
$type, $messagesSupported)) {
847 $redirectUris[] =
"{$this->baseUrl}{$message->path}";
848 $capabilities = $message->capabilities;
849 $variables = array_merge($message->variables, $variables);
850 $constants = array_merge($message->constants, $constants);
853 'target_link_uri' =>
"{$this->baseUrl}{$message->path}",
857 foreach ($capabilities as $capability) {
858 if (array_key_exists($capability, $claimsMapping) && in_array($claimsMapping[$capability], $claimsSupported)) {
859 $claims[] = $claimsMapping[$capability];
864 if (empty($redirectUris)) {
865 $redirectUris = array($toolUrl);
867 $redirectUris = array_unique($redirectUris);
873 foreach ($constants as
$name => $value) {
874 $custom[
$name] = $value;
876 foreach ($variables as
$name => $value) {
877 $custom[
$name] =
'$' . $value;
879 $toolConfig = array();
880 $toolConfig[
'application_type'] =
'web';
881 $toolConfig[
'client_name'] = $toolName;
882 $toolConfig[
'response_types'] = array(
'id_token');
883 $toolConfig[
'grant_types'] = array(
'implicit',
'client_credentials');
884 $toolConfig[
'initiate_login_uri'] = $toolUrl;
885 $toolConfig[
'redirect_uris'] = $redirectUris;
887 $toolConfig[
'token_endpoint_auth_method'] =
'private_key_jwt';
888 $toolConfig[
'https://purl.imsglobal.org/spec/lti-tool-configuration'] = array(
890 'target_link_uri' => $toolUrl,
891 'custom_parameters' => $custom,
894 'description' => $toolDescription
896 $toolConfig[
'scope'] = implode(
' ', array_intersect($this->requiredScopes, $scopesSupported));
897 if (!empty($iconUrl)) {
898 $toolConfig[
'logo_uri'] =
"{$this->baseUrl}{$iconUrl}";
914 $this->ok = !empty($parameters[
'registration_token']);
916 $body = json_encode($toolConfig);
917 $headers =
"Content-type: application/json\n" .
918 "Authorization: Bearer {$parameters['registration_token']}";
919 $http =
new HttpMessage($platformConfig[
'registration_endpoint'],
'POST', $body, $headers);
920 $this->ok =
$http->send();
922 $registrationConfig = json_decode(
$http->response,
true);
923 $this->ok = !empty($registrationConfig);
926 $this->reason =
'Unable to register with platform.';
929 $this->reason =
'Invalid registration request: missing registration_token parameter.';
933 $registrationConfig = null;
936 return $registrationConfig;
948 $domain = $platformConfig[
'issuer'];
949 $pos = strpos($domain,
'//');
950 if ($pos !==
false) {
951 $domain = substr($domain, $pos + 2);
952 $pos = strpos($domain,
'/');
953 if ($pos !==
false) {
954 $domain = substr($domain, 0, $pos);
957 $this->platform =
new Platform($this->dataConnector);
958 $this->platform->name = $domain;
960 $this->platform->signatureMethod = reset($platformConfig[
'id_token_signing_alg_values_supported']);
961 $this->platform->platformId = $platformConfig[
'issuer'];
962 $this->platform->clientId = $registrationConfig[
'client_id'];
963 $this->platform->deploymentId = $registrationConfig[
'https://purl.imsglobal.org/spec/lti-tool-configuration'][
'deployment_id'];
964 $this->platform->authenticationUrl = $platformConfig[
'authorization_endpoint'];
965 $this->platform->accessTokenUrl = $platformConfig[
'token_endpoint'];
966 $this->platform->jku = $platformConfig[
'jwks_uri'];
968 $this->ok = $this->platform->save();
970 $this->reason =
'Sorry, an error occurred when saving the platform details.';
984 if (!empty($this->platform)) {
986 if (!$this->platform->enabled) {
987 $enabled =
', but it will need to be enabled by the tool provider before it can be used';
988 } elseif (!empty($this->platform->enableFrom) && ($this->platform->enableFrom > $now)) {
989 $enabled =
', but you will only have access from ' . date(
'j F Y H:i T', $this->platform->enableFrom);
990 if (!empty($this->platform->enableUntil)) {
991 $enabled .=
' until ' . date(
'j F Y H:i T', $this->platform->enableUntil);
993 } elseif (!empty($this->platform->enableUntil)) {
994 if ($this->platform->enableUntil > $now) {
995 $enabled =
', but you will only have access until ' . date(
'j F Y H:i T', $this->platform->enableUntil);
997 $enabled =
', but your access was set to end at ' . date(
'j F Y H:i T', $this->platform->enableUntil);
1005 <meta
http-equiv=
"content-type" content=
"text/html; charset=UTF-8">
1006 <title>
LTI Tool registration</title>
1014 background-color: #d4edda;
1015 border-color: #c3e6cb;
1017 padding: .75rem 1.25rem;
1018 margin-bottom: 1rem;
1022 background-color: #f8d7da;
1023 border-color: #f5c6cb;
1025 padding: .75rem 1.25rem;
1026 margin-bottom: 1rem;
1032 border: 1px solid transparent;
1033 padding: 0.375rem 0.75rem;
1036 border-radius: 0.25rem;
1038 background-color: #007bff;
1039 border-color: #007bff;
1041 text-decoration: none;
1042 display:
inline-block;
1046 <script
language=
"javascript" type=
"text/javascript">
1047 function doClose(el) {
1048 (window.opener || window.parent).postMessage({subject:
'org.imsglobal.lti.close'},
'*');
1054 <h1>{$toolConfig[
'client_name']} registration</h1>
1060 The tool registration was successful{
$enabled}.
1063 <button type=
"button" onclick=
"return doClose();">Close</button>
1070 Sorry, the registration was not successful: {$this->reason}
1079 $this->output = $html;
1095 if ($ok && $autoEnable) {
1096 $tool->enabled =
true;
1116 $tool->enabled =
true;
1132 $tool->setRecordId($id);
1152 $this->message = self::CONNECTION_ERROR_MESSAGE .
' ' .
$this->reason;
1157 if (!empty($this->returnUrl)) {
1159 if (!is_null($this->platform) && isset($this->messageParameters[
'lti_message_type']) &&
1160 (($this->messageParameters[
'lti_message_type'] ===
'ContentItemSelectionRequest') ||
1161 ($this->messageParameters[
'lti_message_type'] ===
'ContentItemUpdateRequest'))) {
1162 $formParams = array();
1163 if ($this->debugMode && !is_null($this->reason)) {
1164 $formParams[
'lti_errormsg'] =
"Debug error: {$this->reason}";
1167 if (!is_null($this->reason)) {
1168 $formParams[
'lti_errorlog'] =
"Debug error: {$this->reason}";
1171 if (isset($this->messageParameters[
'data'])) {
1172 $formParams[
'data'] = $this->messageParameters[
'data'];
1174 $this->version = (isset($this->messageParameters[
'lti_version'])) ? $this->messageParameters[
'lti_version'] :
Util::LTI_VERSION1;
1175 $page = $this->
sendMessage($errorUrl,
'ContentItemSelection', $formParams);
1178 if (strpos($errorUrl,
'?') ===
false) {
1183 if ($this->debugMode && !is_null($this->reason)) {
1184 $errorUrl .=
'lti_errormsg=' . urlencode(
"Debug error: $this->reason");
1186 $errorUrl .=
'lti_errormsg=' . urlencode($this->message);
1187 if (!is_null($this->reason)) {
1188 $errorUrl .=
'<i_errorlog=' . urlencode(
"Debug error: $this->reason");
1191 header(
"Location: {$errorUrl}");
1195 if (!is_null($this->errorOutput)) {
1197 } elseif ($this->debugMode && !empty($this->reason)) {
1198 echo
"Debug error: {$this->reason}";
1200 echo
"Error: {$this->message}";
1204 } elseif (!is_null($this->redirectUrl)) {
1205 header(
"Location: {$this->redirectUrl}");
1207 } elseif (!is_null($this->output)) {
1221 $doSavePlatform =
false;
1223 if ($this->ok && $strictMode && !empty($this->jwt) && !empty($this->jwt->hasJwt())) {
1224 if (!empty($this->jwt->getClaim(
'https://purl.imsglobal.org/spec/lti/claim/context',
'')) &&
1225 empty($this->messageParameters[
'context_id'])) {
1227 $this->reason =
'Missing id property in https://purl.imsglobal.org/spec/lti/claim/context claim';
1228 } elseif (!empty($this->jwt->getClaim(
'https://purl.imsglobal.org/spec/lti/claim/tool_platform',
'')) &&
1229 empty($this->messageParameters[
'tool_consumer_instance_guid'])) {
1231 $this->reason =
'Missing guid property in https://purl.imsglobal.org/spec/lti/claim/tool_platform claim';
1235 if ($this->messageParameters[
'lti_message_type'] ===
'basic-lti-launch-request') {
1236 $this->ok = isset($this->messageParameters[
'resource_link_id']) && (strlen(trim($this->messageParameters[
'resource_link_id'])) > 0);
1238 $this->reason =
'Missing resource link ID.';
1241 $this->ok = isset($this->messageParameters[
'roles']);
1243 $this->reason =
'Missing roles parameter.';
1246 } elseif (($this->messageParameters[
'lti_message_type'] ===
'ContentItemSelectionRequest') ||
1247 ($this->messageParameters[
'lti_message_type'] ===
'ContentItemUpdateRequest')) {
1248 $isUpdate = ($this->messageParameters[
'lti_message_type'] ===
'ContentItemUpdateRequest');
1249 $mediaTypes = array();
1250 $contentTypes = array();
1251 $fileTypes = array();
1252 if (isset($this->messageParameters[
'accept_media_types']) && (strlen(trim($this->messageParameters[
'accept_media_types'])) > 0)) {
1253 $mediaTypes = array_filter(
1254 explode(
',', str_replace(
' ',
'', $this->messageParameters[
'accept_media_types'])),
1258 $this->ok = (count($mediaTypes) > 0) || ($this->messageParameters[
'lti_version'] ===
Util::LTI_VERSION1P3);
1260 $this->reason =
'No content types specified.';
1261 } elseif ($isUpdate) {
1264 $this->messageParameters[
'accept_media_types'],
1266 'Invalid value in accept_media_types parameter: \'%s\'.',
1273 $this->messageParameters[
'accept_types'],
1275 'Invalid value in accept_types parameter: \'%s\'.',
1283 $mediaTypes = array_unique($mediaTypes);
1284 foreach ($mediaTypes as $mediaType) {
1285 if (strpos($mediaType,
'application/vnd.ims.lti.') !== 0) {
1286 $fileTypes[] = $mediaType;
1288 if (($mediaType ===
'text/html') || ($mediaType ===
'*/*')) {
1291 } elseif ((strpos($mediaType,
'image/') === 0) || ($mediaType ===
'*/*')) {
1299 if (!empty($fileTypes)) {
1302 $contentTypes = array_unique($contentTypes);
1305 if (isset($this->messageParameters[
'accept_presentation_document_targets']) &&
1306 (strlen(trim($this->messageParameters[
'accept_presentation_document_targets'])) > 0)) {
1307 $documentTargets = array_filter(explode(
1309 str_replace(
' ',
'', $this->messageParameters[
'accept_presentation_document_targets'])
1311 $documentTargets = array_unique($documentTargets);
1312 $this->ok = count($documentTargets) > 0;
1314 $this->reason =
'Missing or empty accept_presentation_document_targets parameter.';
1316 if (empty($this->jwt) || !$this->jwt->hasJwt()) {
1317 $permittedTargets = array(
'embed',
'frame',
'iframe',
'window',
'popup',
'overlay',
'none');
1319 $permittedTargets = array(
'embed',
'iframe',
'window');
1321 foreach ($documentTargets as $documentTarget) {
1325 'Invalid value in accept_presentation_document_targets parameter: \'%s\'.',
1339 $this->reason =
'No accept_presentation_document_targets parameter found.';
1343 $this->ok = !empty($this->messageParameters[
'content_item_return_url']);
1345 $this->reason =
'Missing content_item_return_url parameter.';
1352 } elseif ($this->messageParameters[
'lti_message_type'] ===
'ToolProxyRegistrationRequest') {
1353 $this->ok = ((isset($this->messageParameters[
'reg_key']) && (strlen(trim($this->messageParameters[
'reg_key'])) > 0)) && (isset($this->messageParameters[
'reg_password']) && (strlen(trim($this->messageParameters[
'reg_password'])) >
1354 0)) && (isset($this->messageParameters[
'tc_profile_url']) && (strlen(trim($this->messageParameters[
'tc_profile_url'])) >
1355 0)) && (isset($this->messageParameters[
'launch_presentation_return_url']) && (strlen(trim($this->messageParameters[
'launch_presentation_return_url'])) > 0)));
1356 if ($this->debugMode && !$this->ok) {
1357 $this->reason =
'Missing message parameters.';
1359 } elseif ($this->messageParameters[
'lti_message_type'] ===
'LtiStartProctoring') {
1360 $this->ok = isset($this->messageParameters[
'resource_link_id']) && (strlen(trim($this->messageParameters[
'resource_link_id'])) > 0);
1362 $this->reason =
'Missing resource link ID.';
1364 $this->ok = isset($this->messageParameters[
'custom_ap_attempt_number']) && (strlen(trim($this->messageParameters[
'custom_ap_attempt_number'])) > 0) &&
1365 is_numeric($this->messageParameters[
'custom_ap_attempt_number']);
1367 $this->reason =
'Missing or invalid value for attempt number.';
1371 $this->ok = isset($this->messageParameters[
'user_id']) && (strlen(trim($this->messageParameters[
'user_id'])) > 0);
1373 $this->reason =
'Missing user ID.';
1380 if ($this->ok && ($this->messageParameters[
'lti_message_type'] !==
'ToolProxyRegistrationRequest')) {
1381 $this->ok = isset($this->messageParameters[
'oauth_consumer_key']);
1383 $this->reason =
'Missing consumer key.';
1386 $this->ok = !is_null($this->platform->created);
1388 if (empty($this->jwt) || !$this->jwt->hasJwt()) {
1389 $this->reason =
"Consumer key not recognised: {$this->messageParameters['oauth_consumer_key']}";
1391 $this->reason =
"Platform not recognised (Platform ID | Client ID | Deployment ID): {$this->messageParameters['platform_id']} | {$this->messageParameters['oauth_consumer_key']} | {$this->messageParameters['deployment_id']}";
1396 if ($this->messageParameters[
'oauth_signature_method'] !== $this->platform->signatureMethod) {
1397 $this->platform->signatureMethod = $this->messageParameters[
'oauth_signature_method'];
1398 $doSavePlatform =
true;
1400 $today = date(
'Y-m-d', $now);
1401 if (is_null($this->platform->lastAccess)) {
1402 $doSavePlatform =
true;
1404 $last = date(
'Y-m-d', $this->platform->lastAccess);
1405 $doSavePlatform = $doSavePlatform || ($last !== $today);
1407 $this->platform->lastAccess = $now;
1411 if ($this->platform->protected) {
1412 if (!is_null($this->platform->consumerGuid)) {
1413 $this->ok = empty($this->messageParameters[
'tool_consumer_instance_guid']) || ($this->platform->consumerGuid === $this->messageParameters[
'tool_consumer_instance_guid']);
1415 $this->reason =
'Request is from an invalid platform.';
1418 $this->ok = isset($this->messageParameters[
'tool_consumer_instance_guid']);
1420 $this->reason =
'A platform GUID must be included in the launch request.';
1425 $this->ok = $this->platform->enabled;
1427 $this->reason =
'Platform has not been enabled by the tool.';
1431 $this->ok = is_null($this->platform->enableFrom) || ($this->platform->enableFrom <= $now);
1433 $this->ok = is_null($this->platform->enableUntil) || ($this->platform->enableUntil > $now);
1435 $this->reason =
'Platform access has expired.';
1438 $this->reason =
'Platform access is not yet available.';
1444 if (($this->messageParameters[
'lti_message_type'] ===
'ContentItemSelectionRequest') ||
1445 ($this->messageParameters[
'lti_message_type'] ===
'ContentItemUpdateRequest')) {
1446 $isUpdate = ($this->messageParameters[
'lti_message_type'] ===
'ContentItemUpdateRequest');
1447 if (isset($this->messageParameters[
'accept_unsigned'])) {
1449 $this->messageParameters[
'accept_unsigned'],
1450 array(
'true',
'false'),
1451 'Invalid value for accept_unsigned parameter: \'%s\'.',
1455 if ($this->ok && isset($this->messageParameters[
'accept_multiple'])) {
1458 $this->messageParameters[
'accept_multiple'],
1459 array(
'true',
'false'),
1460 'Invalid value for accept_multiple parameter: \'%s\'.',
1465 $this->messageParameters[
'accept_multiple'],
1467 'Invalid value for accept_multiple parameter: \'%s\'.',
1472 if ($this->ok && isset($this->messageParameters[
'accept_copy_advice'])) {
1475 $this->messageParameters[
'accept_copy_advice'],
1476 array(
'true',
'false'),
1477 'Invalid value for accept_copy_advice parameter: \'%s\'.',
1482 $this->messageParameters[
'accept_copy_advice'],
1484 'Invalid value for accept_copy_advice parameter: \'%s\'.',
1489 if ($this->ok && isset($this->messageParameters[
'auto_create'])) {
1491 $this->messageParameters[
'auto_create'],
1492 array(
'true',
'false'),
1493 'Invalid value for auto_create parameter: \'%s\'.',
1497 if ($this->ok && isset($this->messageParameters[
'can_confirm'])) {
1499 $this->messageParameters[
'can_confirm'],
1500 array(
'true',
'false'),
1501 'Invalid value for can_confirm parameter: \'%s\'.',
1506 if ($this->ok && isset($this->messageParameters[
'launch_presentation_document_target'])) {
1508 $this->messageParameters[
'launch_presentation_document_target'],
1509 array(
'embed',
'frame',
'iframe',
'window',
'popup',
'overlay'),
1510 'Invalid value for launch_presentation_document_target parameter: \'%s\'.',
1514 if ($this->ok && ($this->messageParameters[
'lti_message_type'] ===
'LtiStartProctoring') &&
1515 ($this->messageParameters[
'launch_presentation_document_target'] !==
'window')) {
1516 $this->ok = !isset($this->messageParameters[
'launch_presentation_height']) &&
1517 !isset($this->messageParameters[
'launch_presentation_width']);
1519 $this->reason =
'Height and width parameters must only be included for the window document target.';
1526 if ($this->ok && ($this->messageParameters[
'lti_message_type'] ===
'ToolProxyRegistrationRequest')) {
1529 $this->reason =
'Invalid lti_version parameter.';
1532 $url = $this->messageParameters[
'tc_profile_url'];
1533 if (strpos(
$url,
'?') ===
false) {
1539 $http =
new HttpMessage(
$url,
'GET', null,
'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json');
1540 $this->ok =
$http->send();
1542 $this->reason =
'Platform profile not accessible.';
1544 $tcProfile = json_decode(
$http->response);
1545 $this->ok = !is_null($tcProfile);
1547 $this->reason =
'Invalid JSON in platform profile.';
1554 $this->platform->profile = $tcProfile;
1555 $capabilities = $this->platform->profile->capability_offered;
1557 foreach ($this->resourceHandlers as $resourceHandler) {
1558 foreach ($resourceHandler->requiredMessages as $message) {
1559 if (!in_array($message->type, $capabilities)) {
1560 $missing[$message->type] =
true;
1564 foreach ($this->constraints as
$name => $constraint) {
1565 if ($constraint[
'required']) {
1566 if (empty(array_intersect(
1568 array_keys(array_intersect(self::$CUSTOM_SUBSTITUTION_VARIABLES, array(
$name)))
1570 $missing[
$name] =
true;
1574 if (!empty($missing)) {
1576 $this->reason =
'Required capability not offered - \'' . implode(
'\', \
'', array_keys($missing)) .
'\'';
1582 foreach ($this->requiredServices as
$service) {
1583 foreach ($service->formats as
$format) {
1584 if (!$this->
findService($format, $service->actions)) {
1586 $this->reason =
'Required service(s) not offered - ';
1589 $this->reason .=
', ';
1591 $this->reason .=
"'{$format}' [" . implode(
', ', $service->actions) .
']';
1597 if ($this->messageParameters[
'lti_message_type'] ===
'ToolProxyRegistrationRequest') {
1598 $this->platform->profile = $tcProfile;
1599 $this->platform->secret = $this->messageParameters[
'reg_password'];
1600 $this->platform->ltiVersion = $this->messageParameters[
'lti_version'];
1601 $this->platform->name = $tcProfile->product_instance->service_owner->service_owner_name->default_value;
1602 $this->platform->consumerName = $this->platform->name;
1603 $this->platform->consumerVersion =
"{$tcProfile->product_instance->product_info->product_family->code}-{$tcProfile->product_instance->product_info->product_version}";
1604 $this->platform->consumerGuid = $tcProfile->product_instance->guid;
1605 $this->platform->enabled =
true;
1606 $this->platform->protected =
true;
1607 $doSavePlatform =
true;
1610 } elseif ($this->ok && !empty($this->messageParameters[
'custom_tc_profile_url']) && empty($this->platform->profile)) {
1611 $url = $this->messageParameters[
'custom_tc_profile_url'];
1612 if (strpos(
$url,
'?') ===
false) {
1617 $url .=
'lti_version=' . $this->messageParameters[
'lti_version'];
1618 $http =
new HttpMessage(
$url,
'GET', null,
'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json');
1619 if (
$http->send()) {
1620 $tcProfile = json_decode(
$http->response);
1621 if (!is_null($tcProfile)) {
1622 $this->platform->profile = $tcProfile;
1623 $doSavePlatform =
true;
1630 if (isset($this->messageParameters[
'relaunch_url'])) {
1631 if (empty($this->messageParameters[
'platform_state'])) {
1633 $this->reason =
'Missing or empty platform_state parameter.';
1639 $invalidParameters = array();
1640 foreach ($this->constraints as
$name => $constraint) {
1641 if (empty($constraint[
'messages']) || in_array(
1642 $this->messageParameters[
'lti_message_type'],
1643 $constraint[
'messages']
1646 if ($constraint[
'required']) {
1647 if (!isset($this->messageParameters[
$name]) || (strlen(trim($this->messageParameters[$name])) <= 0)) {
1648 $invalidParameters[] =
"{$name} (missing)";
1652 if ($ok && !is_null($constraint[
'max_length']) && isset($this->messageParameters[
$name])) {
1653 if (strlen(trim($this->messageParameters[$name])) > $constraint[
'max_length']) {
1654 $invalidParameters[] =
"{$name} (too long)";
1659 if (count($invalidParameters) > 0) {
1661 if (empty($this->reason)) {
1662 $this->reason =
'Invalid parameter(s): ' . implode(
', ', $invalidParameters) .
'.';
1672 $tpHook =
new $className($this);
1673 $contextId = $tpHook->getContextId();
1675 if (empty($contextId) && isset($this->messageParameters[
'context_id'])) {
1676 $contextId = trim($this->messageParameters[
'context_id']);
1678 if (!empty($contextId)) {
1681 if (isset($this->messageParameters[
'context_title'])) {
1682 $title = trim($this->messageParameters[
'context_title']);
1684 if (empty($title)) {
1685 $title =
"Course {$this->context->getId()}";
1687 $this->context->title = $title;
1688 if (isset($this->messageParameters[
'context_type'])) {
1689 $this->context->type = trim($this->messageParameters[
'context_type']);
1690 if (strpos($this->context->type,
'http://purl.imsglobal.org/vocab/lis/v2/course#') === 0) {
1691 $this->context->type = substr($this->context->type, 46);
1697 if (isset($this->messageParameters[
'resource_link_id'])) {
1698 $contentItemId =
'';
1699 if (isset($this->messageParameters[
'custom_content_item_id'])) {
1700 $contentItemId = $this->messageParameters[
'custom_content_item_id'];
1702 if (empty($this->context)) {
1705 trim($this->messageParameters[
'resource_link_id']),
1711 trim($this->messageParameters[
'resource_link_id']),
1716 if (isset($this->messageParameters[
'resource_link_title'])) {
1717 $title = trim($this->messageParameters[
'resource_link_title']);
1719 if (empty($title)) {
1720 $title =
"Resource {$this->resourceLink->getId()}";
1722 $this->resourceLink->title = $title;
1725 foreach ($this->platform->getSettings() as
$name => $value) {
1726 if ((strpos($name,
'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) {
1727 $this->platform->setSetting($name);
1728 $doSavePlatform =
true;
1731 if (!empty($this->context)) {
1732 foreach ($this->context->getSettings() as $name => $value) {
1733 if ((strpos($name,
'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) {
1734 $this->context->setSetting($name);
1738 if (!empty($this->resourceLink)) {
1739 foreach ($this->resourceLink->getSettings() as $name => $value) {
1740 if ((strpos($name,
'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) {
1741 $this->resourceLink->setSetting($name);
1746 foreach (self::$LTI_CONSUMER_SETTING_NAMES as $name) {
1747 if (isset($this->messageParameters[$name])) {
1748 $this->platform->setSetting($name, $this->messageParameters[$name]);
1749 } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) {
1750 $this->platform->setSetting($name);
1753 if (!empty($this->context)) {
1754 foreach (self::$LTI_CONTEXT_SETTING_NAMES as $name) {
1755 if (isset($this->messageParameters[$name])) {
1756 $this->context->setSetting($name, $this->messageParameters[$name]);
1757 } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) {
1758 $this->context->setSetting($name);
1762 if (!empty($this->resourceLink)) {
1763 foreach (self::$LTI_RESOURCE_LINK_SETTING_NAMES as $name) {
1764 if (isset($this->messageParameters[$name])) {
1765 $this->resourceLink->setSetting($name, $this->messageParameters[$name]);
1766 } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) {
1767 $this->resourceLink->setSetting($name);
1772 foreach ($this->messageParameters as $name => $value) {
1773 if ((strpos($name,
'custom_') === 0) && !in_array(
1776 self::$LTI_CONSUMER_SETTING_NAMES,
1777 self::$LTI_CONTEXT_SETTING_NAMES,
1778 self::$LTI_RESOURCE_LINK_SETTING_NAMES
1781 $this->platform->setSetting($name, $value);
1782 if (!empty($this->context)) {
1783 $this->context->setSetting($name, $value);
1785 if (!empty($this->resourceLink)) {
1786 $this->resourceLink->setSetting($name, $value);
1793 if ($this->
hasConfiguredApiHook(self::$USER_ID_HOOK, $this->platform->getFamilyCode(), $this)) {
1794 $className = $this->
getApiHook(self::$USER_ID_HOOK, $this->platform->getFamilyCode());
1795 $tpHook =
new $className($this);
1796 $userId = $tpHook->getUserId();
1798 if (empty($userId) && isset($this->messageParameters[
'user_id'])) {
1799 $userId = trim($this->messageParameters[
'user_id']);
1805 $firstname = (isset($this->messageParameters[
'lis_person_name_given'])) ? $this->messageParameters[
'lis_person_name_given'] :
'';
1806 $lastname = (isset($this->messageParameters[
'lis_person_name_family'])) ? $this->messageParameters[
'lis_person_name_family'] :
'';
1807 $fullname = (isset($this->messageParameters[
'lis_person_name_full'])) ? $this->messageParameters[
'lis_person_name_full'] :
'';
1808 $this->userResult->setNames($firstname, $lastname, $fullname);
1811 if (isset($this->messageParameters[
'lis_person_sourcedid'])) {
1812 $this->userResult->sourcedId = $this->messageParameters[
'lis_person_sourcedid'];
1816 if (isset($this->messageParameters[
'ext_username'])) {
1817 $this->userResult->username = $this->messageParameters[
'ext_username'];
1818 } elseif (isset($this->messageParameters[
'ext_user_username'])) {
1819 $this->userResult->username = $this->messageParameters[
'ext_user_username'];
1820 } elseif (isset($this->messageParameters[
'custom_username'])) {
1821 $this->userResult->username = $this->messageParameters[
'custom_username'];
1822 } elseif (isset($this->messageParameters[
'custom_user_username'])) {
1823 $this->userResult->username = $this->messageParameters[
'custom_user_username'];
1827 $email = (isset($this->messageParameters[
'lis_person_contact_email_primary'])) ? $this->messageParameters[
'lis_person_contact_email_primary'] :
'';
1828 $this->userResult->setEmail(
$email, $this->defaultEmail);
1831 if (isset($this->messageParameters[
'user_image'])) {
1832 $this->userResult->image = $this->messageParameters[
'user_image'];
1836 if (isset($this->messageParameters[
'roles'])) {
1838 $this->messageParameters[
'roles'],
1839 $this->messageParameters[
'lti_version']
1845 if ($this->platform->ltiVersion !== $this->messageParameters[
'lti_version']) {
1846 $this->platform->ltiVersion = $this->messageParameters[
'lti_version'];
1847 $doSavePlatform =
true;
1849 if (isset($this->messageParameters[
'deployment_id'])) {
1850 $this->platform->deploymentId = $this->messageParameters[
'deployment_id'];
1852 if (isset($this->messageParameters[
'tool_consumer_instance_name'])) {
1853 if ($this->platform->consumerName !== $this->messageParameters[
'tool_consumer_instance_name']) {
1854 $this->platform->consumerName = $this->messageParameters[
'tool_consumer_instance_name'];
1855 $doSavePlatform =
true;
1858 if (isset($this->messageParameters[
'tool_consumer_info_product_family_code'])) {
1859 $version = $this->messageParameters[
'tool_consumer_info_product_family_code'];
1860 if (isset($this->messageParameters[
'tool_consumer_info_version'])) {
1861 $version .=
"-{$this->messageParameters['tool_consumer_info_version']}";
1864 if ($this->platform->consumerVersion !==
$version) {
1865 $this->platform->consumerVersion =
$version;
1866 $doSavePlatform =
true;
1868 } elseif (isset($this->messageParameters[
'ext_lms']) && ($this->platform->consumerName !== $this->messageParameters[
'ext_lms'])) {
1869 $this->platform->consumerVersion = $this->messageParameters[
'ext_lms'];
1870 $doSavePlatform =
true;
1872 if (isset($this->messageParameters[
'tool_consumer_instance_guid'])) {
1873 if (is_null($this->platform->consumerGuid)) {
1874 $this->platform->consumerGuid = $this->messageParameters[
'tool_consumer_instance_guid'];
1875 $doSavePlatform =
true;
1876 } elseif (!$this->platform->protected && ($this->platform->consumerGuid !== $this->messageParameters[
'tool_consumer_instance_guid'])) {
1877 $this->platform->consumerGuid = $this->messageParameters[
'tool_consumer_instance_guid'];
1878 $doSavePlatform =
true;
1881 if (isset($this->messageParameters[
'launch_presentation_css_url'])) {
1882 if ($this->platform->cssPath !== $this->messageParameters[
'launch_presentation_css_url']) {
1883 $this->platform->cssPath = $this->messageParameters[
'launch_presentation_css_url'];
1884 $doSavePlatform =
true;
1886 } elseif (isset($this->messageParameters[
'ext_launch_presentation_css_url']) && ($this->platform->cssPath !== $this->messageParameters[
'ext_launch_presentation_css_url'])) {
1887 $this->platform->cssPath = $this->messageParameters[
'ext_launch_presentation_css_url'];
1888 $doSavePlatform =
true;
1889 } elseif (!empty($this->platform->cssPath)) {
1890 $this->platform->cssPath = null;
1891 $doSavePlatform =
true;
1896 if ($doSavePlatform) {
1897 $this->platform->save();
1902 if (isset($this->context)) {
1903 $this->context->save();
1906 if (isset($this->resourceLink)) {
1908 $this->resourceLink->save();
1911 $this->userResult->setResourceLinkId($this->resourceLink->getRecordId());
1912 if (isset($this->messageParameters[
'lis_result_sourcedid'])) {
1913 if ($this->userResult->ltiResultSourcedId !== $this->messageParameters[
'lis_result_sourcedid']) {
1914 $this->userResult->ltiResultSourcedId = $this->messageParameters[
'lis_result_sourcedid'];
1915 $this->userResult->save();
1917 } elseif ($this->userResult->isLearner()) {
1918 $this->userResult->ltiResultSourcedId =
'';
1919 $this->userResult->save();
1939 $doSaveResourceLink =
true;
1941 $id = $this->resourceLink->primaryResourceLinkId;
1943 $shareRequest = isset($this->messageParameters[
'custom_share_key']) && !empty($this->messageParameters[
'custom_share_key']);
1944 if ($shareRequest) {
1945 if (!$this->allowSharing) {
1947 $this->reason =
'Your sharing request has been refused because sharing is not being permitted.';
1950 $shareKey =
new ResourceLinkShareKey($this->resourceLink, $this->messageParameters[
'custom_share_key']);
1951 if (!is_null($shareKey->resourceLinkId)) {
1953 $id = $shareKey->resourceLinkId;
1954 $ok = (
$id !== $this->resourceLink->getRecordId());
1956 $this->resourceLink->primaryResourceLinkId =
$id;
1957 $this->resourceLink->shareApproved = $shareKey->autoApprove;
1958 $ok = $this->resourceLink->save();
1960 $doSaveResourceLink =
false;
1961 $this->userResult->getResourceLink()->primaryResourceLinkId =
$id;
1962 $this->userResult->getResourceLink()->shareApproved = $shareKey->autoApprove;
1963 $this->userResult->getResourceLink()->updated = time();
1965 $shareKey->delete();
1967 $this->reason =
'An error occurred initialising your share arrangement.';
1970 $this->reason =
'It is not possible to share your resource link with yourself.';
1974 $ok = !is_null(
$id);
1976 $this->reason =
'You have requested to share a resource link but none is available.';
1978 $ok = (!is_null($this->userResult->getResourceLink()->shareApproved) && $this->userResult->getResourceLink()->shareApproved);
1980 $this->reason =
'Your share request is waiting to be approved.';
1989 $this->reason =
'You have not requested to share a resource link but an arrangement is currently in place.';
1994 if ($ok && !is_null(
$id)) {
1996 $ok = !is_null($resourceLink->created);
1998 if ($doSaveResourceLink) {
1999 $this->resourceLink->save();
2003 $this->reason =
'Unable to load resource link being shared.';
2018 if (isset($parameters[
'client_id'])) {
2021 $deploymentId = null;
2022 if (isset($parameters[
'lti_deployment_id'])) {
2023 $deploymentId = $parameters[
'lti_deployment_id'];
2028 $this->debugMode =
true;
2031 $ok = !is_null($this->platform) && !empty($this->platform->authenticationUrl);
2033 $this->reason =
'Platform not found or no platform authentication request URL.';
2037 $ok = !$nonce->load();
2039 $nonce->expires = time() + 10;
2040 $ok = $nonce->save();
2043 $oauthRequest = LTIOAuth\OAuthRequest::from_request();
2044 $redirectUri = $oauthRequest->get_normalized_http_url();
2045 if (!empty(
$_SERVER[
'QUERY_STRING'])) {
2046 if (
$_SERVER[
'REQUEST_METHOD'] ===
'POST') {
2047 $redirectUri .=
"?{$_SERVER['QUERY_STRING']}";
2053 $parts = explode(
'=', $param, 2);
2056 array(
'iss',
'target_link_uri',
'login_hint',
'lti_message_hint',
'client_id',
'lti_deployment_id')
2059 } elseif (!$ignore) {
2061 $queryString .=
"&{$parts[0]}";
2063 $queryString .=
"&{$parts[0]}={$parts[1]}";
2067 if (!empty($queryString)) {
2068 $queryString = substr($queryString, 1);
2069 $redirectUri .=
"?{$queryString}";
2074 'client_id' => $this->platform->clientId,
2075 'login_hint' => $parameters[
'login_hint'],
2078 'redirect_uri' => $redirectUri,
2079 'response_mode' =>
'form_post',
2080 'response_type' =>
'id_token',
2081 'scope' =>
'openid',
2082 'state' => $nonce->getValue()
2084 if (isset($parameters[
'lti_message_hint'])) {
2085 $params[
'lti_message_hint'] = $parameters[
'lti_message_hint'];
2094 $this->reason =
'Unable to generate a state value.';
2108 $ok = !$nonce->load();
2110 $ok = $nonce->save();
2113 'tool_state' => $nonce->getValue(),
2114 'platform_state' => $this->messageParameters[
'platform_state']
2116 $params = $this->platform->addSignature($this->messageParameters[
'relaunch_url'], $params);
2117 $this->output =
Util::sendForm($this->messageParameters[
'relaunch_url'], $params);
2119 $this->reason =
'Unable to generate a state value.';
2132 private function checkValue(&$value, array $values,
string $reason,
bool $strictMode,
bool $ignoreInvalid =
false): bool
2134 $lookupValue = $value;
2136 $lookupValue = strtolower($value);
2138 $ok = in_array($lookupValue, $values);
2139 if (!$ok && !$strictMode && $ignoreInvalid) {
2140 Util::logInfo(sprintf($reason, $value) .
" [Error ignored]");
2142 } elseif (!$ok && !empty($reason)) {
2143 $this->reason = sprintf($reason, $value);
2144 } elseif ($lookupValue !== $value) {
2145 Util::logInfo(sprintf($reason, $value) .
" [Changed to '{$lookupValue}']");
2146 $value = $lookupValue;
const TYPE_LINK
Type for link content-item.
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
const LTI_ASSIGNMENT_MEDIA_TYPE
Media type for LTI assignment links.
const TYPE_HTML
Type for HTML content-item.
const TYPE_IMAGE
Type for image content-item.
$messages
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static http()
Fetches the global http state from ILIAS.
const TYPE_LTI_LINK
Type for LTI link content-item.
static fromPlatform(Platform $platform, string $ltiContextId)
Class constructor from consumer.
Class to represent a content-item object.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const TYPE_LTI_ASSIGNMENT
Type for LTI assignment content-item.
const TYPE_FILE
Type for file content-item.
const LTI_LINK_MEDIA_TYPE
Media type for LTI launch links.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class to represent a platform context.