ILIAS  release_8 Revision v8.24
Tool.php
Go to the documentation of this file.
1<?php
2
20
26use ILIAS\LTI\ToolProvider\Http\HTTPMessage;
30
38class Tool
39{
40 use System;
41 use ApiHook;
42
46 public const CONNECTION_ERROR_MESSAGE = 'Sorry, there was an error connecting you to the application.';
47
51 public const ID_SCOPE_ID_ONLY = 0;
52
56 public const ID_SCOPE_GLOBAL = 1;
57
61 public const ID_SCOPE_CONTEXT = 2;
62
66 public const ID_SCOPE_RESOURCE = 3;
67
71 public const ID_SCOPE_SEPARATOR = ':';
72
76 public static array $MESSAGE_TYPES = array(
77 'basic-lti-launch-request',
78 'ConfigureLaunchRequest',
79 'DashboardRequest',
80 'ContentItemSelectionRequest',
81 'ContentItemUpdateRequest',
82 'ToolProxyRegistrationRequest',
83 'LtiStartProctoring',
84 'LtiEndAssessment'
85 );
86
90 private static array $LTI_CONSUMER_SETTING_NAMES = array('custom_tc_profile_url', 'custom_system_setting_url', 'custom_oauth2_access_token_url');
91
95 private static array $LTI_CONTEXT_SETTING_NAMES = array('custom_context_setting_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'
100 );
101
105 private static array $LTI_RESOURCE_LINK_SETTING_NAMES = array('lis_result_sourcedid', 'lis_outcome_service_url',
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',
111 'custom_ap_acs_url'
112 );
113
117 private static array $LTI_RETAIN_SETTING_NAMES = array('custom_lineitem_url');
118
122 private static array $CUSTOM_SUBSTITUTION_VARIABLES = array('User.id' => 'user_id',
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'
156 );
157
158// /**
159// * Tool consumer object.
160// *
161// * @deprecated Use Tool::$platform instead
162// * @see platform
163// *
164// * @var ToolConsumer|null $consumer
165// */
166// public ?ToolConsumer $consumer = null;
167
173 public \ilLTIPlatform $platform;
174
180 public ?string $returnUrl = null;
181
187 public ?UserResult $userResult = null;
188
195
201 public ?Context $context = null;
202
208 public string $defaultEmail = '';
209
216
222 public bool $allowSharing = false;
223
229 public ?string $message = null;
230
236 public ?string $baseUrl = null;
237
243 public ?Item $vendor = null;
244
250 public ?Item $product = null;
251
257 public ?array $requiredServices = null;
258
264 public ?array $optionalServices = null;
265
271 public ?array $resourceHandlers = null;
272
278 public ?string $messageUrl = null;
279
285 public ?string $initiateLoginUrl = null;
286
292 public ?array $redirectionUris = null;
293
299 public static ?Tool $defaultTool = null;
300
306 public static bool $authenticateUsingGet = false;
307
313 protected ?string $redirectUrl = null;
314
320 protected ?array $mediaTypes = null;
321
327 protected ?array $contentTypes = null;
328
334 protected ?array $fileTypes = null;
335
341 protected ?array $documentTargets = null;
342
348 protected ?string $output = null;
349
355 protected ?string $errorOutput = null;
356
362 private ?array $constraints = null;
363
369 {
370// $this->consumer = &$this->platform; //UK: deprecated
371 $this->initialize();
372 if (empty($dataConnector)) {
374 }
375 $this->dataConnector = $dataConnector;
376 }
377
381 public function initialize()
382 {
383 $this->id = null;
384 $this->key = null;
385 $this->name = null;
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;
394 $this->settings = array();
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();
402// $this->vendor = new Profile\Item(); //Changed UK
403 $this->vendor = new \ILIAS\LTI\ToolProvider\Content\Item(null);
404// $this->product = new Profile\Item();
405 $this->product = new \ILIAS\LTI\ToolProvider\Content\Item(null);
406 $this->requiredServices = array();
407 $this->optionalServices = array();
408 $this->resourceHandlers = array();
409 }
410
416 public function save(): bool
417 {
418 return $this->dataConnector->saveTool($this);
419 }
420
426 public function delete(): bool
427 {
428 return $this->dataConnector->deleteTool($this);
429 }
430
436 public function getMessageParameters(): array
437 {
438 if (is_null($this->messageParameters)) {
439 $this->parseMessage();
440 // Set debug mode
442 $this->debugMode = (isset($this->messageParameters['custom_debug']) &&
443 (strtolower($this->messageParameters['custom_debug']) === 'true'));
444 if ($this->debugMode) {
446 }
447 }
448 // Set return URL if available
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'];
453 }
454 if (empty($this->returnUrl) && !empty($this->messageParameters['launch_presentation_return_url'])) {
455 $this->returnUrl = $this->messageParameters['launch_presentation_return_url'];
456 }
457 }
458
460 }
461
466 public function handleRequest(bool $strictMode = false)
467 {
468 $parameters = Util::getRequestParameters();
469 if ($this->debugMode) {
471 }
472 if ($_SERVER['REQUEST_METHOD'] === 'HEAD') { // Ignore HEAD requests
473 Util::logRequest(true);
474 } elseif (isset($parameters['iss']) && (strlen($parameters['iss']) > 0)) { // Initiate login request
476 if (!isset($parameters['login_hint']) || (strlen($parameters['login_hint']) <= 0)) {
477 $this->ok = false;
478 $this->reason = 'Missing login_hint parameter';
479 } elseif (!isset($parameters['target_link_uri']) || (strlen($parameters['target_link_uri']) <= 0)) {
480 $this->ok = false;
481 $this->reason = 'Missing target_link_uri parameter';
482 } else {
483 $this->ok = $this->sendAuthenticationRequest($parameters);
484 }
485 } elseif (isset($parameters['openid_configuration']) && (strlen($parameters['openid_configuration']) > 0)) { // Dynamic registration request
487 $this->onRegistration();
488 } else { // LTI message
489 $this->getMessageParameters();
491 if ($this->ok && $this->authenticate($strictMode)) {
492 if (empty($this->output)) {
493 $this->doCallback();
494 if ($this->ok && ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest')) {
495 $this->platform->save();
496 }
497 }
498 }
499 }
500 if (!$this->ok) {
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}";
506 }
507 }
508 Util::logError($errorMessage);
509 }
510
511 $this->result();
512 }
513
521 public function setParameterConstraint(string $name, bool $required = true, int $maxLength = null, array $messageTypes = null)
522 {
523 $name = trim($name);
524 if (!empty($name)) {
525 $this->constraints[$name] = array('required' => $required, 'max_length' => $maxLength, 'messages' => $messageTypes);
526 }
527 }
528
529// /**
530// * Get an array of defined tool consumers
531// *
532// * @deprecated Use getPlatforms() instead
533// * @see Tool::getPlatforms()
534// *
535// * @return array Array of ToolConsumer objects
536// */
537// public function getConsumers() : array
538// {
539// Util::logDebug(
540// 'Method ceLTIc\LTI\Tool::getConsumers() has been deprecated; please use ceLTIc\LTI\Tool::getPlatforms() instead.',
541// true
542// );
543// return $this->getPlatforms();
544// }
545
551 public function getPlatforms(): array
552 {
553 return $this->dataConnector->getPlatforms();
554 }
555
562 public function findService(string $format, array $methods)
563 {
564 $found = false;
565 $services = $this->platform->profile->service_offered;
566 if (is_array($services)) {
567 $n = -1;
568 foreach ($services as $service) {
569 $n++;
570 if (!is_array($service->format) || !in_array($format, $service->format)) {
571 continue;
572 }
573 $missing = array();
574 foreach ($methods as $method) {
575 if (!is_array($service->action) || !in_array($method, $service->action)) {
576 $missing[] = $method;
577 }
578 }
579 $methods = $missing;
580 if (count($methods) <= 0) {
581 $found = $service;
582 break;
583 }
584 }
585 }
586
587 return $found;
588 }
589
590 //not necessary because LTI V 2
591// /**
592// * Send the tool proxy to the platform
593// *
594// * @return bool True if the tool proxy was accepted
595// */
596// public function doToolProxyService() : bool
597// {
598// // Create tool proxy
599// $toolProxyService = $this->findService('application/vnd.ims.lti.v2.toolproxy+json', array('POST'));
600// $secret = Util::getRandomString(12);
601// $toolProxy = new MediaType\ToolProxy($this, $toolProxyService, $secret);
602// $http = $this->platform->doServiceRequest($toolProxyService, 'POST', 'application/vnd.ims.lti.v2.toolproxy+json',
603// json_encode($toolProxy));
604// $ok = $http->ok && ($http->status === 201) && !empty($http->responseJson->tool_proxy_guid);
605// if ($ok) {
606// $this->platform->setKey($http->responseJson->tool_proxy_guid);
607// $this->platform->secret = $toolProxy->security_contract->shared_secret;
608// $this->platform->toolProxy = $toolProxy; //UK: changed from json_encode($toolProxy);
609// $this->platform->save();
610// }
611//
612// return $ok;
613// }
614
615// /**
616// * Generate a web page containing an auto-submitted form of parameters.
617// * @param string $url URL to which the form should be submitted
618// * @param array $params Array of form parameters
619// * @param string $target Name of target (optional)
620// * @return string
621// *@deprecated Use Util::sendForm() instead
622// * @see Util::sendForm()
623// */
624// public static function sendForm(string $url, array $params, string $target = '') : string
625// {
626// Util::logDebug('Method ceLTIc\LTI\Tool::sendForm() has been deprecated; please use ceLTIc\LTI\Util::sendForm() instead.',
627// true);
628// Util::sendForm($url, $params, $target);
629// }
630
631 ###
632 ### PROTECTED METHODS
633 ###
634
638 protected function onLaunch()
639 {
640 $this->reason = 'No onLaunch method found for tool';
641 $this->onError();
642 }
643
647 protected function onConfigure()
648 {
649 $this->reason = 'No onConfigure method found for tool';
650 $this->onError();
651 }
652
656 protected function onDashboard()
657 {
658 $this->reason = 'No onDashboard method found for tool';
659 $this->onError();
660 }
661
665 protected function onContentItem()
666 {
667 $this->reason = 'No onContentItem method found for tool';
668 $this->onError();
669 }
670
674 protected function onContentItemUpdate()
675 {
676 $this->reason = 'No onContentItemUpdate method found for tool';
677 $this->onError();
678 }
679
683 protected function onRegister()
684 {
685 $this->reason = 'No onRegister method found for tool';
686 $this->onError();
687 }
688
692 protected function onRegistration()
693 {
694 $platformConfig = $this->getPlatformConfiguration();
695 if ($this->ok) {
696 $toolConfig = $this->getConfiguration($platformConfig);
697 $registrationConfig = $this->sendRegistration($platformConfig, $toolConfig);
698 if ($this->ok) {
699 $this->getPlatformToRegister($platformConfig, $registrationConfig);
700 }
701 }
702 $this->getRegistrationResponsePage($toolConfig);
703 $this->ok = true;
704 }
705
709 protected function onLtiStartProctoring()
710 {
711 $this->reason = 'No onLtiStartProctoring method found for tool';
712 $this->onError();
713 }
714
718 protected function onLtiEndAssessment()
719 {
720 $this->reason = 'No onLtiEndAssessment method found for tool';
721 $this->onError();
722 }
723
730 protected function onInitiateLogin(array $requestParameters, array &$authParameters)
731 {
732 }
733
737 protected function onError()
738 {
739 $this->ok = false;
740 }
741
747 protected function getPlatformConfiguration(): ?array
748 {
749 if ($this->ok) {
750 $parameters = Util::getRequestParameters();
751 $this->ok = !empty($parameters['openid_configuration']);
752 if ($this->ok) {
753 $http = new HttpMessage($parameters['openid_configuration']);
754 $this->ok = $http->send();
755 if ($this->ok) {
756 $platformConfig = json_decode($http->response, true);
757 $this->ok = !empty($platformConfig);
758 }
759 if (!$this->ok) {
760 $this->reason = 'Unable to access platform configuration details.';
761 }
762 } else {
763 $this->reason = 'Invalid registration request: missing openid_configuration parameter.';
764 }
765 if ($this->ok) {
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']);
773 if (!$this->ok) {
774 $this->reason = 'Invalid platform configuration details.';
775 }
776 }
777 if ($this->ok) {
778 Jwt::setJwtClient(); //added - check
779 $jwtClient = Jwt::getJwtClient();
780 $algorithms = \array_intersect(
781 $jwtClient::getSupportedAlgorithms(),
782 $platformConfig['id_token_signing_alg_values_supported']
783 );
784 $this->ok = !empty($algorithms);
785 if ($this->ok) {
786 rsort($platformConfig['id_token_signing_alg_values_supported']);
787 } else {
788 $this->reason = 'None of the signature algorithms offered by the platform is supported.';
789 }
790 }
791 }
792 if (!$this->ok) {
793 $platformConfig = null;
794 }
795
796 return $platformConfig;
797 }
798
804 protected function getConfiguration(array $platformConfig): array
805 {
806 $claimsMapping = array(
807 'User.id' => 'sub',
808 'Person.name.full' => 'name',
809 'Person.name.given' => 'given_name',
810 'Person.name.family' => 'family_name',
811 'Person.email.primary' => 'email'
812 );
813 $toolName = (!empty($this->product->name)) ? $this->product->name : 'Unnamed tool';
814 $toolDescription = (!empty($this->product->description)) ? $this->product->description : '';
815// $oauthRequest = OAuth\OAuthRequest::from_request();
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'];
824 $iconUrl = null;
825 $messages = array();
826 $claims = array('iss');
827 $variables = array();
828 $constants = array();
829 $redirectUris = array();
830 foreach ($this->resourceHandlers as $resourceHandler) {
831 if (empty($iconUrl)) {
832 $iconUrl = $resourceHandler->icon;
833 }
834 foreach (array_merge($resourceHandler->optionalMessages, $resourceHandler->requiredMessages) as $message) {
835 $type = $message->type;
836 if (array_key_exists($type, Util::MESSAGE_TYPE_MAPPING)) {
838 }
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);
851 $messages[] = array(
852 'type' => $type,
853 'target_link_uri' => "{$this->baseUrl}{$message->path}",
854 'label' => $toolName
855 );
856 }
857 foreach ($capabilities as $capability) {
858 if (array_key_exists($capability, $claimsMapping) && in_array($claimsMapping[$capability], $claimsSupported)) {
859 $claims[] = $claimsMapping[$capability];
860 }
861 }
862 }
863 }
864 if (empty($redirectUris)) {
865 $redirectUris = array($toolUrl);
866 } else {
867 $redirectUris = array_unique($redirectUris);
868 }
869 if (!empty($claims)) {
870 $claims = array_unique($claims);
871 }
872 $custom = array();
873 foreach ($constants as $name => $value) {
874 $custom[$name] = $value;
875 }
876 foreach ($variables as $name => $value) {
877 $custom[$name] = '$' . $value;
878 }
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;
886 $toolConfig['jwks_uri'] = $this->jku;
887 $toolConfig['token_endpoint_auth_method'] = 'private_key_jwt';
888 $toolConfig['https://purl.imsglobal.org/spec/lti-tool-configuration'] = array(
889 'domain' => $domain,
890 'target_link_uri' => $toolUrl,
891 'custom_parameters' => $custom,
892 'claims' => $claims,
893 'messages' => $messages,
894 'description' => $toolDescription
895 );
896 $toolConfig['scope'] = implode(' ', array_intersect($this->requiredScopes, $scopesSupported));
897 if (!empty($iconUrl)) {
898 $toolConfig['logo_uri'] = "{$this->baseUrl}{$iconUrl}";
899 }
900
901 return $toolConfig;
902 }
903
910 protected function sendRegistration(array $platformConfig, array $toolConfig): ?array
911 {
912 if ($this->ok) {
913 $parameters = Util::getRequestParameters();
914 $this->ok = !empty($parameters['registration_token']);
915 if ($this->ok) {
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();
921 if ($this->ok) {
922 $registrationConfig = json_decode($http->response, true);
923 $this->ok = !empty($registrationConfig);
924 }
925 if (!$this->ok) {
926 $this->reason = 'Unable to register with platform.';
927 }
928 } else {
929 $this->reason = 'Invalid registration request: missing registration_token parameter.';
930 }
931 }
932 if (!$this->ok) {
933 $registrationConfig = null;
934 }
935
936 return $registrationConfig;
937 }
938
946 protected function getPlatformToRegister(array $platformConfig, array $registrationConfig, bool $doSave = true): ?Platform
947 {
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);
955 }
956 }
957 $this->platform = new Platform($this->dataConnector);
958 $this->platform->name = $domain;
959 $this->platform->ltiVersion = Util::LTI_VERSION1P3;
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'];
967 if ($doSave) {
968 $this->ok = $this->platform->save();
969 if (!$this->ok) {
970 $this->reason = 'Sorry, an error occurred when saving the platform details.';
971 }
972 }
973
974 return $this->platform;
975 }
976
981 protected function getRegistrationResponsePage(array $toolConfig)
982 {
983 $enabled = '';
984 if (!empty($this->platform)) {
985 $now = time();
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);
992 }
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);
996 } else {
997 $enabled = ', but your access was set to end at ' . date('j F Y H:i T', $this->platform->enableUntil);
998 }
999 }
1000 }
1001 $html = <<< EOD
1002<!DOCTYPE html>
1003<html lang="en">
1004 <head>
1005 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
1006 <title>LTI Tool registration</title>
1007 <style>
1008 h1 {
1009 font-soze: 110%;
1010 font-weight: bold;
1011 }
1012 .success {
1013 color: #155724;
1014 background-color: #d4edda;
1015 border-color: #c3e6cb;
1016 border: 1px solid;
1017 padding: .75rem 1.25rem;
1018 margin-bottom: 1rem;
1019 }
1020 .error {
1021 color: #721c24;
1022 background-color: #f8d7da;
1023 border-color: #f5c6cb;
1024 border: 1px solid;
1025 padding: .75rem 1.25rem;
1026 margin-bottom: 1rem;
1027 }
1028 .centre {
1029 text-align: center;
1030 }
1031 button {
1032 border: 1px solid transparent;
1033 padding: 0.375rem 0.75rem;
1034 font-size: 1rem;
1035 line-height: 1.5;
1036 border-radius: 0.25rem;
1037 color: #fff;
1038 background-color: #007bff;
1039 border-color: #007bff;
1040 text-align: center;
1041 text-decoration: none;
1042 display: inline-block;
1043 cursor: pointer;
1044 }
1045 </style>
1046 <script language="javascript" type="text/javascript">
1047 function doClose(el) {
1048 (window.opener || window.parent).postMessage({subject:'org.imsglobal.lti.close'}, '*');
1049 return true;
1050 }
1051 </script>
1052 </head>
1053 <body>
1054 <h1>{$toolConfig['client_name']} registration</h1>
1055
1056EOD;
1057 if ($this->ok) {
1058 $html .= <<< EOD
1059 <p class="success">
1060 The tool registration was successful{$enabled}.
1061 </p>
1062 <p class="centre">
1063 <button type="button" onclick="return doClose();">Close</button>
1064 </p>
1065
1066EOD;
1067 } else {
1068 $html .= <<< EOD
1069 <p class="error">
1070 Sorry, the registration was not successful: {$this->reason}
1071 </p>
1072
1073EOD;
1074 }
1075 $html .= <<< EOD
1076 </body>
1077</html>
1078EOD;
1079 $this->output = $html;
1080 }
1081
1089 public static function fromConsumerKey(string $key = null, DataConnector $dataConnector = null, bool $autoEnable = false): Tool
1090 {
1091 $tool = new static($dataConnector);
1092 $tool->key = $key;
1093 if (!empty($dataConnector)) {
1094 $ok = $dataConnector->loadTool($tool);
1095 if ($ok && $autoEnable) {
1096 $tool->enabled = true;
1097 }
1098 }
1099
1100 return $tool;
1101 }
1102
1110 public static function fromInitiateLoginUrl(string $initiateLoginUrl, DataConnector $dataConnector = null, bool $autoEnable = false): Tool
1111 {
1112 $tool = new static($dataConnector);
1113 $tool->initiateLoginUrl = $initiateLoginUrl;
1114 if ($dataConnector->loadTool($tool)) {
1115 if ($autoEnable) {
1116 $tool->enabled = true;
1117 }
1118 }
1119
1120 return $tool;
1121 }
1122
1129 public static function fromRecordId(string $id, DataConnector $dataConnector): Tool
1130 {
1131 $tool = new static($dataConnector);
1132 $tool->setRecordId($id);
1133 $dataConnector->loadTool($tool);
1134
1135 return $tool;
1136 }
1137
1138 ###
1139 ### PRIVATE METHODS
1140 ###
1141
1149 private function result(): void
1150 {
1151 if (!$this->ok) {
1152 $this->message = self::CONNECTION_ERROR_MESSAGE . ' ' . $this->reason;
1153 $this->onError();
1154 }
1155 if (!$this->ok) {
1156 // If not valid, return an error message to the platform if a return URL is provided
1157 if (!empty($this->returnUrl)) {
1158 $errorUrl = $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}";
1165 } else {
1166 $formParams['lti_errormsg'] = $this->message;
1167 if (!is_null($this->reason)) {
1168 $formParams['lti_errorlog'] = "Debug error: {$this->reason}";
1169 }
1170 }
1171 if (isset($this->messageParameters['data'])) {
1172 $formParams['data'] = $this->messageParameters['data'];
1173 }
1174 $this->version = (isset($this->messageParameters['lti_version'])) ? $this->messageParameters['lti_version'] : Util::LTI_VERSION1;
1175 $page = $this->sendMessage($errorUrl, 'ContentItemSelection', $formParams);
1176 echo $page;
1177 } else {
1178 if (strpos($errorUrl, '?') === false) {
1179 $errorUrl .= '?';
1180 } else {
1181 $errorUrl .= '&';
1182 }
1183 if ($this->debugMode && !is_null($this->reason)) {
1184 $errorUrl .= 'lti_errormsg=' . urlencode("Debug error: $this->reason");
1185 } else {
1186 $errorUrl .= 'lti_errormsg=' . urlencode($this->message);
1187 if (!is_null($this->reason)) {
1188 $errorUrl .= '&lti_errorlog=' . urlencode("Debug error: $this->reason");
1189 }
1190 }
1191 header("Location: {$errorUrl}");
1192 }
1193 exit;
1194 } else {
1195 if (!is_null($this->errorOutput)) {
1196 echo $this->errorOutput;
1197 } elseif ($this->debugMode && !empty($this->reason)) {
1198 echo "Debug error: {$this->reason}";
1199 } else {
1200 echo "Error: {$this->message}";
1201 }
1202 exit;
1203 }
1204 } elseif (!is_null($this->redirectUrl)) {
1205 header("Location: {$this->redirectUrl}");
1206 exit;
1207 } elseif (!is_null($this->output)) {
1208 echo $this->output;
1209 exit;
1210 }
1211 }
1212
1219 private function authenticate(bool $strictMode): bool
1220 {
1221 $doSavePlatform = false;
1222 $this->ok = $this->checkMessage();
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'])) {
1226 $this->ok = false;
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'])) {
1230 $this->ok = false;
1231 $this->reason = 'Missing guid property in https://purl.imsglobal.org/spec/lti/claim/tool_platform claim';
1232 }
1233 }
1234 if ($this->ok) {
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);
1237 if (!$this->ok) {
1238 $this->reason = 'Missing resource link ID.';
1239 }
1240 if ($this->ok && ($this->messageParameters['lti_version'] === Util::LTI_VERSION1P3)) {
1241 $this->ok = isset($this->messageParameters['roles']);
1242 if (!$this->ok) {
1243 $this->reason = 'Missing roles parameter.';
1244 }
1245 }
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'])),
1255 'strlen'
1256 );
1257 }
1258 $this->ok = (count($mediaTypes) > 0) || ($this->messageParameters['lti_version'] === Util::LTI_VERSION1P3);
1259 if (!$this->ok) {
1260 $this->reason = 'No content types specified.';
1261 } elseif ($isUpdate) {
1262 if ($this->messageParameters['lti_version'] !== Util::LTI_VERSION1P3) {
1263 if (!$this->checkValue(
1264 $this->messageParameters['accept_media_types'],
1266 'Invalid value in accept_media_types parameter: \'%s\'.',
1267 $strictMode,
1268 true
1269 )) {
1270 $this->ok = false;
1271 }
1272 } elseif (!$this->checkValue(
1273 $this->messageParameters['accept_types'],
1275 'Invalid value in accept_types parameter: \'%s\'.',
1276 $strictMode,
1277 true
1278 )) {
1279 $this->ok = false;
1280 }
1281 }
1282 if ($this->ok) {
1283 $mediaTypes = array_unique($mediaTypes);
1284 foreach ($mediaTypes as $mediaType) {
1285 if (strpos($mediaType, 'application/vnd.ims.lti.') !== 0) {
1286 $fileTypes[] = $mediaType;
1287 }
1288 if (($mediaType === 'text/html') || ($mediaType === '*/*')) {
1291 } elseif ((strpos($mediaType, 'image/') === 0) || ($mediaType === '*/*')) {
1293 } elseif ($mediaType === Item::LTI_LINK_MEDIA_TYPE) {
1295 } elseif ($mediaType === Item::LTI_ASSIGNMENT_MEDIA_TYPE) {
1297 }
1298 }
1299 if (!empty($fileTypes)) {
1301 }
1302 $contentTypes = array_unique($contentTypes);
1303 }
1304 if ($this->ok) {
1305 if (isset($this->messageParameters['accept_presentation_document_targets']) &&
1306 (strlen(trim($this->messageParameters['accept_presentation_document_targets'])) > 0)) {
1307 $documentTargets = array_filter(explode(
1308 ',',
1309 str_replace(' ', '', $this->messageParameters['accept_presentation_document_targets'])
1310 ), 'strlen');
1311 $documentTargets = array_unique($documentTargets);
1312 $this->ok = count($documentTargets) > 0;
1313 if (!$this->ok) {
1314 $this->reason = 'Missing or empty accept_presentation_document_targets parameter.';
1315 } else {
1316 if (empty($this->jwt) || !$this->jwt->hasJwt()) {
1317 $permittedTargets = array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay', 'none');
1318 } else { // JWT
1319 $permittedTargets = array('embed', 'iframe', 'window');
1320 }
1321 foreach ($documentTargets as $documentTarget) {
1322 if (!$this->checkValue(
1323 $documentTarget,
1324 $permittedTargets,
1325 'Invalid value in accept_presentation_document_targets parameter: \'%s\'.',
1326 $strictMode,
1327 true
1328 )) {
1329 $this->ok = false;
1330 break;
1331 }
1332 }
1333 if ($this->ok) {
1334 $this->documentTargets = $documentTargets;
1335 }
1336 }
1337 } else {
1338 $this->ok = false;
1339 $this->reason = 'No accept_presentation_document_targets parameter found.';
1340 }
1341 }
1342 if ($this->ok) {
1343 $this->ok = !empty($this->messageParameters['content_item_return_url']);
1344 if (!$this->ok) {
1345 $this->reason = 'Missing content_item_return_url parameter.';
1346 } else {
1347 $this->mediaTypes = $mediaTypes;
1348 $this->contentTypes = $contentTypes;
1349 $this->fileTypes = $fileTypes;
1350 }
1351 }
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.';
1358 }
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);
1361 if (!$this->ok) {
1362 $this->reason = 'Missing resource link ID.';
1363 } else {
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']);
1366 if (!$this->ok) {
1367 $this->reason = 'Missing or invalid value for attempt number.';
1368 }
1369 }
1370 if ($this->ok) {
1371 $this->ok = isset($this->messageParameters['user_id']) && (strlen(trim($this->messageParameters['user_id'])) > 0);
1372 if (!$this->ok) {
1373 $this->reason = 'Missing user ID.';
1374 }
1375 }
1376 }
1377 }
1378 $now = time();
1379 // Check consumer key
1380 if ($this->ok && ($this->messageParameters['lti_message_type'] !== 'ToolProxyRegistrationRequest')) {
1381 $this->ok = isset($this->messageParameters['oauth_consumer_key']);
1382 if (!$this->ok) {
1383 $this->reason = 'Missing consumer key.';
1384 }
1385 if ($this->ok) {
1386 $this->ok = !is_null($this->platform->created);
1387 if (!$this->ok) {
1388 if (empty($this->jwt) || !$this->jwt->hasJwt()) {
1389 $this->reason = "Consumer key not recognised: {$this->messageParameters['oauth_consumer_key']}";
1390 } else {
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']}";
1392 }
1393 }
1394 }
1395 if ($this->ok) {
1396 if ($this->messageParameters['oauth_signature_method'] !== $this->platform->signatureMethod) {
1397 $this->platform->signatureMethod = $this->messageParameters['oauth_signature_method'];
1398 $doSavePlatform = true;
1399 }
1400 $today = date('Y-m-d', $now);
1401 if (is_null($this->platform->lastAccess)) {
1402 $doSavePlatform = true;
1403 } else {
1404 $last = date('Y-m-d', $this->platform->lastAccess);
1405 $doSavePlatform = $doSavePlatform || ($last !== $today);
1406 }
1407 $this->platform->lastAccess = $now;
1408 $this->ok = $this->verifySignature();
1409 }
1410 if ($this->ok) {
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']);
1414 if (!$this->ok) {
1415 $this->reason = 'Request is from an invalid platform.';
1416 }
1417 } else {
1418 $this->ok = isset($this->messageParameters['tool_consumer_instance_guid']);
1419 if (!$this->ok) {
1420 $this->reason = 'A platform GUID must be included in the launch request.';
1421 }
1422 }
1423 }
1424 if ($this->ok) {
1425 $this->ok = $this->platform->enabled;
1426 if (!$this->ok) {
1427 $this->reason = 'Platform has not been enabled by the tool.';
1428 }
1429 }
1430 if ($this->ok) {
1431 $this->ok = is_null($this->platform->enableFrom) || ($this->platform->enableFrom <= $now);
1432 if ($this->ok) {
1433 $this->ok = is_null($this->platform->enableUntil) || ($this->platform->enableUntil > $now);
1434 if (!$this->ok) {
1435 $this->reason = 'Platform access has expired.';
1436 }
1437 } else {
1438 $this->reason = 'Platform access is not yet available.';
1439 }
1440 }
1441 }
1442 // Validate other message parameter values
1443 if ($this->ok) {
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'])) {
1448 $this->ok = $this->checkValue(
1449 $this->messageParameters['accept_unsigned'],
1450 array('true', 'false'),
1451 'Invalid value for accept_unsigned parameter: \'%s\'.',
1452 $strictMode
1453 );
1454 }
1455 if ($this->ok && isset($this->messageParameters['accept_multiple'])) {
1456 if (!$isUpdate) {
1457 $this->ok = $this->checkValue(
1458 $this->messageParameters['accept_multiple'],
1459 array('true', 'false'),
1460 'Invalid value for accept_multiple parameter: \'%s\'.',
1461 $strictMode
1462 );
1463 } else {
1464 $this->ok = $this->checkValue(
1465 $this->messageParameters['accept_multiple'],
1466 array('false'),
1467 'Invalid value for accept_multiple parameter: \'%s\'.',
1468 $strictMode
1469 );
1470 }
1471 }
1472 if ($this->ok && isset($this->messageParameters['accept_copy_advice'])) {
1473 if (!$isUpdate) {
1474 $this->ok = $this->checkValue(
1475 $this->messageParameters['accept_copy_advice'],
1476 array('true', 'false'),
1477 'Invalid value for accept_copy_advice parameter: \'%s\'.',
1478 $strictMode
1479 );
1480 } else {
1481 $this->ok = $this->checkValue(
1482 $this->messageParameters['accept_copy_advice'],
1483 array('false'),
1484 'Invalid value for accept_copy_advice parameter: \'%s\'.',
1485 $strictMode
1486 );
1487 }
1488 }
1489 if ($this->ok && isset($this->messageParameters['auto_create'])) {
1490 $this->ok = $this->checkValue(
1491 $this->messageParameters['auto_create'],
1492 array('true', 'false'),
1493 'Invalid value for auto_create parameter: \'%s\'.',
1494 $strictMode
1495 );
1496 }
1497 if ($this->ok && isset($this->messageParameters['can_confirm'])) {
1498 $this->ok = $this->checkValue(
1499 $this->messageParameters['can_confirm'],
1500 array('true', 'false'),
1501 'Invalid value for can_confirm parameter: \'%s\'.',
1502 $strictMode
1503 );
1504 }
1505 }
1506 if ($this->ok && isset($this->messageParameters['launch_presentation_document_target'])) {
1507 $this->ok = $this->checkValue(
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\'.',
1511 $strictMode,
1512 true
1513 );
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']);
1518 if (!$this->ok) {
1519 $this->reason = 'Height and width parameters must only be included for the window document target.';
1520 }
1521 }
1522 }
1523 }
1524 }
1525
1526 if ($this->ok && ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest')) {
1527 $this->ok = $this->messageParameters['lti_version'] === Util::LTI_VERSION2;
1528 if (!$this->ok) {
1529 $this->reason = 'Invalid lti_version parameter.';
1530 }
1531 if ($this->ok) {
1532 $url = $this->messageParameters['tc_profile_url'];
1533 if (strpos($url, '?') === false) {
1534 $url .= '?';
1535 } else {
1536 $url .= '&';
1537 }
1538 $url .= 'lti_version=' . Util::LTI_VERSION2;
1539 $http = new HttpMessage($url, 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json');
1540 $this->ok = $http->send();
1541 if (!$this->ok) {
1542 $this->reason = 'Platform profile not accessible.';
1543 } else {
1544 $tcProfile = json_decode($http->response);
1545 $this->ok = !is_null($tcProfile);
1546 if (!$this->ok) {
1547 $this->reason = 'Invalid JSON in platform profile.';
1548 }
1549 }
1550 }
1551 // Check for required capabilities
1552 if ($this->ok) {
1553 $this->platform = Platform::fromConsumerKey($this->messageParameters['reg_key'], $this->dataConnector);
1554 $this->platform->profile = $tcProfile;
1555 $capabilities = $this->platform->profile->capability_offered;
1556 $missing = array();
1557 foreach ($this->resourceHandlers as $resourceHandler) {
1558 foreach ($resourceHandler->requiredMessages as $message) {
1559 if (!in_array($message->type, $capabilities)) {
1560 $missing[$message->type] = true;
1561 }
1562 }
1563 }
1564 foreach ($this->constraints as $name => $constraint) {
1565 if ($constraint['required']) {
1566 if (empty(array_intersect(
1567 $capabilities,
1568 array_keys(array_intersect(self::$CUSTOM_SUBSTITUTION_VARIABLES, array($name)))
1569 ))) {
1570 $missing[$name] = true;
1571 }
1572 }
1573 }
1574 if (!empty($missing)) {
1575 ksort($missing);
1576 $this->reason = 'Required capability not offered - \'' . implode('\', \'', array_keys($missing)) . '\'';
1577 $this->ok = false;
1578 }
1579 }
1580 // Check for required services
1581 if ($this->ok) {
1582 foreach ($this->requiredServices as $service) {
1583 foreach ($service->formats as $format) {
1584 if (!$this->findService($format, $service->actions)) {
1585 if ($this->ok) {
1586 $this->reason = 'Required service(s) not offered - ';
1587 $this->ok = false;
1588 } else {
1589 $this->reason .= ', ';
1590 }
1591 $this->reason .= "'{$format}' [" . implode(', ', $service->actions) . ']';
1592 }
1593 }
1594 }
1595 }
1596 if ($this->ok) {
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;
1608 }
1609 }
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) {
1613 $url .= '?';
1614 } else {
1615 $url .= '&';
1616 }
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;
1624 }
1625 }
1626 }
1627
1628 if ($this->ok) {
1629 // Check if a relaunch is being requested
1630 if (isset($this->messageParameters['relaunch_url'])) {
1631 if (empty($this->messageParameters['platform_state'])) {
1632 $this->ok = false;
1633 $this->reason = 'Missing or empty platform_state parameter.';
1634 } else {
1635 $this->sendRelaunchRequest();
1636 }
1637 } else {
1638 // Validate message parameter constraints
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']
1644 )) {
1645 $ok = true;
1646 if ($constraint['required']) {
1647 if (!isset($this->messageParameters[$name]) || (strlen(trim($this->messageParameters[$name])) <= 0)) {
1648 $invalidParameters[] = "{$name} (missing)";
1649 $ok = false;
1650 }
1651 }
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)";
1655 }
1656 }
1657 }
1658 }
1659 if (count($invalidParameters) > 0) {
1660 $this->ok = false;
1661 if (empty($this->reason)) {
1662 $this->reason = 'Invalid parameter(s): ' . implode(', ', $invalidParameters) . '.';
1663 }
1664 }
1665
1666 if ($this->ok) {
1667 // Set the request context
1668 $contextId = '';
1669 //UK: Check if necessary
1670 if ($this->hasConfiguredApiHook(self::$CONTEXT_ID_HOOK, $this->platform->getFamilyCode(), $this)) {
1671 $className = $this->getApiHook(self::$CONTEXT_ID_HOOK, $this->platform->getFamilyCode());
1672 $tpHook = new $className($this);
1673 $contextId = $tpHook->getContextId();
1674 }
1675 if (empty($contextId) && isset($this->messageParameters['context_id'])) {
1676 $contextId = trim($this->messageParameters['context_id']);
1677 }
1678 if (!empty($contextId)) {
1679 $this->context = Context::fromPlatform($this->platform, $contextId);
1680 $title = '';
1681 if (isset($this->messageParameters['context_title'])) {
1682 $title = trim($this->messageParameters['context_title']);
1683 }
1684 if (empty($title)) {
1685 $title = "Course {$this->context->getId()}";
1686 }
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);
1692 }
1693 }
1694 }
1695
1696 // Set the request resource link
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'];
1701 }
1702 if (empty($this->context)) {
1703 $this->resourceLink = ResourceLink::fromPlatform(
1704 $this->platform,
1705 trim($this->messageParameters['resource_link_id']),
1706 $contentItemId
1707 );
1708 } else {
1709 $this->resourceLink = ResourceLink::fromContext(
1710 $this->context,
1711 trim($this->messageParameters['resource_link_id']),
1712 $contentItemId
1713 );
1714 }
1715 $title = '';
1716 if (isset($this->messageParameters['resource_link_title'])) {
1717 $title = trim($this->messageParameters['resource_link_title']);
1718 }
1719 if (empty($title)) {
1720 $title = "Resource {$this->resourceLink->getId()}";
1721 }
1722 $this->resourceLink->title = $title;
1723 }
1724 // Delete any existing custom parameters
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;
1729 }
1730 }
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);
1735 }
1736 }
1737 }
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);
1742 }
1743 }
1744 }
1745 // Save LTI parameters
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);
1751 }
1752 }
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);
1759 }
1760 }
1761 }
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);
1768 }
1769 }
1770 }
1771 // Save other custom parameters at all levels
1772 foreach ($this->messageParameters as $name => $value) {
1773 if ((strpos($name, 'custom_') === 0) && !in_array(
1774 $name,
1775 array_merge(
1776 self::$LTI_CONSUMER_SETTING_NAMES,
1777 self::$LTI_CONTEXT_SETTING_NAMES,
1778 self::$LTI_RESOURCE_LINK_SETTING_NAMES
1779 )
1780 )) {
1781 $this->platform->setSetting($name, $value);
1782 if (!empty($this->context)) {
1783 $this->context->setSetting($name, $value);
1784 }
1785 if (!empty($this->resourceLink)) {
1786 $this->resourceLink->setSetting($name, $value);
1787 }
1788 }
1789 }
1790
1791 // Set the user instance
1792 $userId = '';
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();
1797 }
1798 if (empty($userId) && isset($this->messageParameters['user_id'])) {
1799 $userId = trim($this->messageParameters['user_id']);
1800 }
1801
1802 $this->userResult = UserResult::fromResourceLink($this->resourceLink, $userId);
1803
1804 // Set the user name
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);
1809
1810 // Set the sourcedId
1811 if (isset($this->messageParameters['lis_person_sourcedid'])) {
1812 $this->userResult->sourcedId = $this->messageParameters['lis_person_sourcedid'];
1813 }
1814
1815 // Set the username
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'];
1824 }
1825
1826 // Set the user email
1827 $email = (isset($this->messageParameters['lis_person_contact_email_primary'])) ? $this->messageParameters['lis_person_contact_email_primary'] : '';
1828 $this->userResult->setEmail($email, $this->defaultEmail);
1829
1830 // Set the user image URI
1831 if (isset($this->messageParameters['user_image'])) {
1832 $this->userResult->image = $this->messageParameters['user_image'];
1833 }
1834
1835 // Set the user roles
1836 if (isset($this->messageParameters['roles'])) {
1837 $this->userResult->roles = self::parseRoles(
1838 $this->messageParameters['roles'],
1839 $this->messageParameters['lti_version']
1840 );
1841 }
1842
1843 // Initialise the platform and check for changes
1844 $this->platform->defaultEmail = $this->defaultEmail;
1845 if ($this->platform->ltiVersion !== $this->messageParameters['lti_version']) {
1846 $this->platform->ltiVersion = $this->messageParameters['lti_version'];
1847 $doSavePlatform = true;
1848 }
1849 if (isset($this->messageParameters['deployment_id'])) {
1850 $this->platform->deploymentId = $this->messageParameters['deployment_id'];
1851 }
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;
1856 }
1857 }
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']}";
1862 }
1863 // do not delete any existing consumer version if none is passed
1864 if ($this->platform->consumerVersion !== $version) {
1865 $this->platform->consumerVersion = $version;
1866 $doSavePlatform = true;
1867 }
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;
1871 }
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;
1879 }
1880 }
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;
1885 }
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;
1892 }
1893 }
1894
1895 // Persist changes to platform
1896 if ($doSavePlatform) {
1897 $this->platform->save();
1898 }
1899
1900 if ($this->ok) {
1901 // Persist changes to cpntext
1902 if (isset($this->context)) {
1903 $this->context->save();
1904 }
1905
1906 if (isset($this->resourceLink)) {
1907 // Persist changes to resource link
1908 $this->resourceLink->save();
1909
1910 // Persist changes to user instnce
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();
1916 }
1917 } elseif ($this->userResult->isLearner()) { // Ensure all learners are recorded in case Assignment and Grade services are used
1918 $this->userResult->ltiResultSourcedId = '';
1919 $this->userResult->save();
1920 }
1921
1922 // Check if a share arrangement is in place for this resource link
1923 $this->ok = $this->checkForShare();
1924 }
1925 }
1926 }
1927 }
1928 return $this->ok;
1929 }
1930
1936 private function checkForShare(): bool
1937 {
1938 $ok = true;
1939 $doSaveResourceLink = true;
1940
1941 $id = $this->resourceLink->primaryResourceLinkId;
1942
1943 $shareRequest = isset($this->messageParameters['custom_share_key']) && !empty($this->messageParameters['custom_share_key']);
1944 if ($shareRequest) {
1945 if (!$this->allowSharing) {
1946 $ok = false;
1947 $this->reason = 'Your sharing request has been refused because sharing is not being permitted.';
1948 } else {
1949 // Check if this is a new share key
1950 $shareKey = new ResourceLinkShareKey($this->resourceLink, $this->messageParameters['custom_share_key']);
1951 if (!is_null($shareKey->resourceLinkId)) {
1952 // Update resource link with sharing primary resource link details
1953 $id = $shareKey->resourceLinkId;
1954 $ok = ($id !== $this->resourceLink->getRecordId());
1955 if ($ok) {
1956 $this->resourceLink->primaryResourceLinkId = $id;
1957 $this->resourceLink->shareApproved = $shareKey->autoApprove;
1958 $ok = $this->resourceLink->save();
1959 if ($ok) {
1960 $doSaveResourceLink = false;
1961 $this->userResult->getResourceLink()->primaryResourceLinkId = $id;
1962 $this->userResult->getResourceLink()->shareApproved = $shareKey->autoApprove;
1963 $this->userResult->getResourceLink()->updated = time();
1964 // Remove share key
1965 $shareKey->delete();
1966 } else {
1967 $this->reason = 'An error occurred initialising your share arrangement.';
1968 }
1969 } else {
1970 $this->reason = 'It is not possible to share your resource link with yourself.';
1971 }
1972 }
1973 if ($ok) {
1974 $ok = !is_null($id);
1975 if (!$ok) {
1976 $this->reason = 'You have requested to share a resource link but none is available.';
1977 } else {
1978 $ok = (!is_null($this->userResult->getResourceLink()->shareApproved) && $this->userResult->getResourceLink()->shareApproved);
1979 if (!$ok) {
1980 $this->reason = 'Your share request is waiting to be approved.';
1981 }
1982 }
1983 }
1984 }
1985 } else {
1986 // Check no share is in place
1987 $ok = is_null($id);
1988 if (!$ok) {
1989 $this->reason = 'You have not requested to share a resource link but an arrangement is currently in place.';
1990 }
1991 }
1992
1993 // Look up primary resource link
1994 if ($ok && !is_null($id)) {
1995 $resourceLink = ResourceLink::fromRecordId($id, $this->dataConnector);
1996 $ok = !is_null($resourceLink->created);
1997 if ($ok) {
1998 if ($doSaveResourceLink) {
1999 $this->resourceLink->save();
2000 }
2001 $this->resourceLink = $resourceLink;
2002 } else {
2003 $this->reason = 'Unable to load resource link being shared.';
2004 }
2005 }
2006
2007 return $ok;
2008 }
2009
2015 private function sendAuthenticationRequest(array $parameters): bool
2016 {
2017 $clientId = null;
2018 if (isset($parameters['client_id'])) {
2019 $clientId = $parameters['client_id'];
2020 }
2021 $deploymentId = null;
2022 if (isset($parameters['lti_deployment_id'])) {
2023 $deploymentId = $parameters['lti_deployment_id'];
2024 }
2025 $currentLogLevel = Util::$logLevel;
2026 $this->platform = \ilLTIPlatform::fromPlatformId($parameters['iss'], $clientId, $deploymentId, $this->dataConnector);
2027 if ($this->platform->debugMode && ($currentLogLevel < Util::LOGLEVEL_INFO)) {
2028 $this->debugMode = true;
2030 }
2031 $ok = !is_null($this->platform) && !empty($this->platform->authenticationUrl);
2032 if (!$ok) {
2033 $this->reason = 'Platform not found or no platform authentication request URL.';
2034 } else {
2035 do {
2036 $nonce = new PlatformNonce($this->platform, Util::getRandomString());
2037 $ok = !$nonce->load();
2038 } while (!$ok);
2039 $nonce->expires = time() + 10; // Expire after 10 seconds
2040 $ok = $nonce->save();
2041 if ($ok) {
2042// $oauthRequest = OAuth\OAuthRequest::from_request();
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']}";
2048 } else { // Remove all parameters added by platform from query string
2049 $queryString = '';
2050 $params = explode('&', $_SERVER['QUERY_STRING']);
2051 $ignore = false; // Only include those query parameters which come before any of the standard OpenID Connect ones
2052 foreach ($params as $param) {
2053 $parts = explode('=', $param, 2);
2054 if (in_array(
2055 $parts[0],
2056 array('iss', 'target_link_uri', 'login_hint', 'lti_message_hint', 'client_id', 'lti_deployment_id')
2057 )) {
2058 $ignore = true;
2059 } elseif (!$ignore) {
2060 if ((count($parts) <= 1) || empty($parts[1])) { // Drop equals sign for empty parameters to workaround Canvas bug
2061 $queryString .= "&{$parts[0]}";
2062 } else {
2063 $queryString .= "&{$parts[0]}={$parts[1]}";
2064 }
2065 }
2066 }
2067 if (!empty($queryString)) {
2068 $queryString = substr($queryString, 1);
2069 $redirectUri .= "?{$queryString}";
2070 }
2071 }
2072 }
2073 $params = array(
2074 'client_id' => $this->platform->clientId,
2075 'login_hint' => $parameters['login_hint'],
2076 'nonce' => Util::getRandomString(32),
2077 'prompt' => 'none',
2078 'redirect_uri' => $redirectUri,
2079 'response_mode' => 'form_post',
2080 'response_type' => 'id_token',
2081 'scope' => 'openid',
2082 'state' => $nonce->getValue()
2083 );
2084 if (isset($parameters['lti_message_hint'])) {
2085 $params['lti_message_hint'] = $parameters['lti_message_hint'];
2086 }
2087 $this->onInitiateLogin($parameters, $params);
2089 $this->output = Util::sendForm($this->platform->authenticationUrl, $params);
2090 } else {
2091 Util::redirect($this->platform->authenticationUrl, $params);
2092 }
2093 } else {
2094 $this->reason = 'Unable to generate a state value.';
2095 }
2096 }
2097
2098 return $ok;
2099 }
2100
2104 private function sendRelaunchRequest()
2105 {
2106 do {
2107 $nonce = new PlatformNonce($this->platform, Util::getRandomString());
2108 $ok = !$nonce->load();
2109 } while (!$ok);
2110 $ok = $nonce->save();
2111 if ($ok) {
2112 $params = array(
2113 'tool_state' => $nonce->getValue(),
2114 'platform_state' => $this->messageParameters['platform_state']
2115 );
2116 $params = $this->platform->addSignature($this->messageParameters['relaunch_url'], $params);
2117 $this->output = Util::sendForm($this->messageParameters['relaunch_url'], $params);
2118 } else {
2119 $this->reason = 'Unable to generate a state value.';
2120 }
2121 }
2122
2132 private function checkValue(&$value, array $values, string $reason, bool $strictMode, bool $ignoreInvalid = false): bool
2133 {
2134 $lookupValue = $value;
2135 if (!$strictMode) {
2136 $lookupValue = strtolower($value);
2137 }
2138 $ok = in_array($lookupValue, $values);
2139 if (!$ok && !$strictMode && $ignoreInvalid) {
2140 Util::logInfo(sprintf($reason, $value) . " [Error ignored]");
2141 $ok = true;
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;
2147 }
2148
2149 return $ok;
2150 }
2151}
$version
Definition: plugin.php:24
Class to represent a content-item object.
Definition: Item.php:32
const TYPE_IMAGE
Type for image content-item.
Definition: Item.php:61
const TYPE_FILE
Type for file content-item.
Definition: Item.php:51
const TYPE_LINK
Type for link content-item.
Definition: Item.php:36
const TYPE_LTI_LINK
Type for LTI link content-item.
Definition: Item.php:41
const TYPE_LTI_ASSIGNMENT
Type for LTI assignment content-item.
Definition: Item.php:46
const LTI_LINK_MEDIA_TYPE
Media type for LTI launch links.
Definition: Item.php:66
const TYPE_HTML
Type for HTML content-item.
Definition: Item.php:56
const LTI_ASSIGNMENT_MEDIA_TYPE
Media type for LTI assignment links.
Definition: Item.php:71
Class to represent a platform context.
Definition: Context.php:34
static fromPlatform(Platform $platform, string $ltiContextId)
Class constructor from platform.
Definition: Context.php:695
Class to provide a connection to a persistent store for LTI objects.
static getDataConnector(object $db=null, string $dbTableNamePrefix='', string $type='')
Create data connector object.
Class to represent an HTTP message request.
Definition: Jwt.php:31
static getJwtClient()
Get the JWT client to use for handling JWTs.
Definition: Jwt.php:85
static setJwtClient(\ILIAS\LTI\ToolProvider\Jwt\ClientInterface $jwtClient=null)
Set the JWT client to use for handling JWTs.
Definition: Jwt.php:74
Class to represent a platform nonce.
Class to represent a platform.
Definition: Platform.php:36
static fromConsumerKey(string $key=null, DataConnector $dataConnector=null, bool $autoEnable=false)
Load the platform from the database by its consumer key.
Definition: Platform.php:496
Class to represent a platform resource link share key.
Class to represent an LTI Tool.
Definition: Tool.php:39
ResourceLink $resourceLink
Resource link object.
Definition: Tool.php:194
checkForShare()
Check if a share arrangement is in place.
Definition: Tool.php:1936
static bool $authenticateUsingGet
Use GET method for authentication request messages when true.
Definition: Tool.php:306
save()
Save the tool to the database.
Definition: Tool.php:416
onRegister()
Process a valid tool proxy registration request.
Definition: Tool.php:683
static array $LTI_RESOURCE_LINK_SETTING_NAMES
Names of LTI parameters to be retained in the resource link settings property.
Definition: Tool.php:105
string $initiateLoginUrl
Initiate Login request URL for Tool.
Definition: Tool.php:285
array $contentTypes
Content item types accepted by the platform.
Definition: Tool.php:327
onContentItemUpdate()
Process a valid content-item update request.
Definition: Tool.php:674
result()
Perform the result of an action.
Definition: Tool.php:1149
string $baseUrl
Base URL for tool service.
Definition: Tool.php:236
array $mediaTypes
Media types accepted by the platform.
Definition: Tool.php:320
static Tool $defaultTool
Default tool for use with service requests.
Definition: Tool.php:299
array $documentTargets
Document targets accepted by the platform.
Definition: Tool.php:341
onInitiateLogin(array $requestParameters, array &$authParameters)
Process a login initiation request.
Definition: Tool.php:730
checkValue(&$value, array $values, string $reason, bool $strictMode, bool $ignoreInvalid=false)
Validate a parameter value from an array of permitted values.
Definition: Tool.php:2132
string $output
Default HTML to be displayed on a successful completion of the request.
Definition: Tool.php:348
Item $vendor
Vendor details.
Definition: Tool.php:243
const ID_SCOPE_RESOURCE
Prefix the ID with the consumer key and resource ID.
Definition: Tool.php:66
Context $context
Context object.
Definition: Tool.php:201
static array $LTI_CONTEXT_SETTING_NAMES
Names of LTI parameters to be retained in the context settings property.
Definition: Tool.php:95
__construct(DataConnector $dataConnector=null)
Class constructor.
Definition: Tool.php:368
getMessageParameters()
Get the message parameters.
Definition: Tool.php:436
onLtiStartProctoring()
Process a valid start proctoring request.
Definition: Tool.php:709
const ID_SCOPE_GLOBAL
Prefix an ID with the consumer key.
Definition: Tool.php:56
findService(string $format, array $methods)
Find an offered service based on a media type and HTTP action(s)
Definition: Tool.php:562
const CONNECTION_ERROR_MESSAGE
Default connection error message.
Definition: Tool.php:46
onError()
Process a response to an invalid request.
Definition: Tool.php:737
const ID_SCOPE_ID_ONLY
Use ID value only.
Definition: Tool.php:51
static array $LTI_RETAIN_SETTING_NAMES
Names of LTI parameters to be retained even when not passed.
Definition: Tool.php:117
handleRequest(bool $strictMode=false)
Process an incoming request.
Definition: Tool.php:466
array $optionalServices
Optional services used by Tool.
Definition: Tool.php:264
onLaunch()
Process a valid launch request.
Definition: Tool.php:638
array $requiredServices
Services required by Tool.
Definition: Tool.php:257
getPlatformToRegister(array $platformConfig, array $registrationConfig, bool $doSave=true)
Initialise the platform to be registered.
Definition: Tool.php:946
onDashboard()
Process a valid dashboard request.
Definition: Tool.php:656
onLtiEndAssessment()
Process a valid end assessment request.
Definition: Tool.php:718
static fromInitiateLoginUrl(string $initiateLoginUrl, DataConnector $dataConnector=null, bool $autoEnable=false)
Load the tool from the database by its initiate login URL.
Definition: Tool.php:1110
ilLTIPlatform $platform
Platform object.
Definition: Tool.php:173
static fromRecordId(string $id, DataConnector $dataConnector)
Load the tool from the database by its record ID.
Definition: Tool.php:1129
bool $allowSharing
Whether shared resource link arrangements are permitted.
Definition: Tool.php:222
array $fileTypes
File types accepted by the platform.
Definition: Tool.php:334
const ID_SCOPE_SEPARATOR
Character used to separate each element of an ID.
Definition: Tool.php:71
static array $LTI_CONSUMER_SETTING_NAMES
Names of LTI parameters to be retained in the consumer settings property.
Definition: Tool.php:90
array $redirectionUris
Redirection URIs for Tool.
Definition: Tool.php:292
Item $product
Product details.
Definition: Tool.php:250
getPlatformConfiguration()
Fetch a platform's configuration data.
Definition: Tool.php:747
initialize()
Initialise the tool.
Definition: Tool.php:381
string $defaultEmail
Default email domain.
Definition: Tool.php:208
array $resourceHandlers
Resource handlers for Tool.
Definition: Tool.php:271
onRegistration()
Process a dynamic registration request.
Definition: Tool.php:692
static array $MESSAGE_TYPES
List of supported incoming message types.
Definition: Tool.php:76
static fromConsumerKey(string $key=null, DataConnector $dataConnector=null, bool $autoEnable=false)
Load the tool from the database by its consumer key.
Definition: Tool.php:1089
getConfiguration(array $platformConfig)
Prepare the tool's configuration data.
Definition: Tool.php:804
const ID_SCOPE_CONTEXT
Prefix the ID with the consumer key and context ID.
Definition: Tool.php:61
sendRegistration(array $platformConfig, array $toolConfig)
Send the tool registration to the platform.
Definition: Tool.php:910
getRegistrationResponsePage(array $toolConfig)
Prepare the page to complete a registration request.
Definition: Tool.php:981
string $redirectUrl
URL to redirect user to on successful completion of the request.
Definition: Tool.php:313
UserResult $userResult
UserResult object.
Definition: Tool.php:187
sendRelaunchRequest()
Generate a form to perform a relaunch request.
Definition: Tool.php:2104
onConfigure()
Process a valid configure request.
Definition: Tool.php:647
getPlatforms()
Get an array of defined platforms.
Definition: Tool.php:551
int $idScope
Scope to use for user IDs.
Definition: Tool.php:215
static array $CUSTOM_SUBSTITUTION_VARIABLES
Names of LTI custom parameter substitution variables (or capabilities) and their associated default m...
Definition: Tool.php:122
string $message
Message for last request processed.
Definition: Tool.php:229
authenticate(bool $strictMode)
Check the authenticity of the LTI message.
Definition: Tool.php:1219
onContentItem()
Process a valid content-item request.
Definition: Tool.php:665
setParameterConstraint(string $name, bool $required=true, int $maxLength=null, array $messageTypes=null)
Add a parameter constraint to be checked on launch.
Definition: Tool.php:521
string $messageUrl
Message URL for Tool.
Definition: Tool.php:278
sendAuthenticationRequest(array $parameters)
Generate a form to perform an authentication request.
Definition: Tool.php:2015
string $returnUrl
Return URL provided by platform.
Definition: Tool.php:180
array $constraints
LTI parameter constraints for auto validation checks.
Definition: Tool.php:362
string $errorOutput
HTML to be displayed on an unsuccessful completion of the request and no return URL is available.
Definition: Tool.php:355
Class to represent a platform user.
Definition: UserResult.php:31
static fromResourceLink(ResourceLink $resourceLink, string $ltiUserId)
Class constructor from resource link.
Definition: UserResult.php:265
const LTI_VERSION1
LTI version 1 for messages.
Definition: Util.php:28
static redirect(string $url, array $params)
Redirect to a URL with query parameters.
Definition: Util.php:523
static logInfo(string $message, bool $showSource=false)
Log an information message.
Definition: Util.php:350
const LOGLEVEL_DEBUG
Log all messages.
Definition: Util.php:156
static sendForm(string $url, array $params, string $target='')
Generate a web page containing an auto-submitted form of parameters.
Definition: Util.php:466
static logRequest(bool $debugLevel=false)
Log a request received.
Definition: Util.php:375
static int $logLevel
Current logging level.
Definition: Util.php:189
const LTI_VERSION2
LTI version 2 for messages.
Definition: Util.php:38
const LTI_VERSION1P3
LTI version 1.3 for messages.
Definition: Util.php:33
const LOGLEVEL_INFO
Log error and information messages.
Definition: Util.php:151
static getRandomString(int $length=8)
Generate a random string.
Definition: Util.php:558
static logError(string $message, bool $showSource=true)
Log an error message.
Definition: Util.php:337
static getRequestParameters()
Return GET and POST request parameters (POST parameters take precedence)
Definition: Util.php:224
const MESSAGE_TYPE_MAPPING
Mapping for standard message types.
Definition: Util.php:48
static fromPlatformId(string $platformId, string $clientId, string $deploymentId, ilLTIDataConnector $dataConnector=null, bool $autoEnable=false)
Load the platform from the database by its platform, client and deployment IDs.
exit
Definition: login.php:28
$clientId
Definition: ltiregend.php:27
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
Definition: ltiregstart.php:33
$service
Definition: ltiservices.php:43
$claims
Definition: ltitoken.php:71
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
if($format !==null) $name
Definition: metadata.php:247
$format
Definition: metadata.php:235
if( $orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:302
static http()
Fetches the global http state from ILIAS.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: OAuth.php:21
trait ApiHook
Trait to handle API hook registrations.
Definition: ApiHook.php:29
static string $CONTEXT_ID_HOOK
Context Id hook name.
Definition: ApiHook.php:38
static getApiHook(string $hookName, string $familyCode)
Get the class name for an API hook.
Definition: ApiHook.php:88
static hasConfiguredApiHook(string $hookName, string $familyCode, $sourceObject)
Check if an API hook is registered and configured.
Definition: ApiHook.php:115
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Message.php:19
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Item.php:19
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: AccessToken.php:19
array $messageParameters
LTI message parameters.
Definition: System.php:179
ilLTIDataConnector $dataConnector
Data connector object.
Definition: System.php:64
static parseRoles($roles, string $ltiVersion=Util::LTI_VERSION1)
Get an array of fully qualified user roles.
Definition: System.php:538
checkMessage()
Verify the required properties of an LTI message.
Definition: System.php:728
string $jku
Endpoint for public key.
Definition: System.php:95
string $reason
Error message for last request processed.
Definition: System.php:102
bool $enabled
Whether the system instance is enabled to accept connection requests.
Definition: System.php:123
parseMessage()
Parse the message.
Definition: System.php:885
string $key
Consumer key/client ID value.
Definition: System.php:193
sendMessage(string $url, string $type, array $messageParams, string $target='', ?string $userId=null, string $hint='')
Generate a web page containing an auto-submitted form of LTI message parameters.
Definition: System.php:644
doCallback()
Call any callback function for the requested action.
Definition: System.php:1141
int $id
System ID value.
Definition: System.php:186
verifySignature()
Verify the signature of a message.
Definition: System.php:781
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$type
$url
$_SERVER['HTTP_HOST']
Definition: raiseError.php:10
$http
Definition: raiseError.php:7
$messages
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: xapiexit.php:22
$param
Definition: xapitoken.php:46