ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
ToolProvider.php
Go to the documentation of this file.
1<?php
2
4
11
22{
23
27 const CONNECTION_ERROR_MESSAGE = 'Sorry, there was an error connecting you to the application.';
28
32 const LTI_VERSION1 = 'LTI-1p0';
36 const LTI_VERSION2 = 'LTI-2p0';
44 const ID_SCOPE_GLOBAL = 1;
56 const ID_SCOPE_SEPARATOR = ':';
57
61 private static $LTI_VERSIONS = array(self::LTI_VERSION1, self::LTI_VERSION2);
65 private static $MESSAGE_TYPES = array('basic-lti-launch-request' => 'onLaunch',
66 'ContentItemSelectionRequest' => 'onContentItem',
67 'ToolProxyRegistrationRequest' => 'register');
73 private static $METHOD_NAMES = array('basic-lti-launch-request' => 'onLaunch',
74 'ContentItemSelectionRequest' => 'onContentItem',
75 'ToolProxyRegistrationRequest' => 'onRegister');
81 private static $LTI_CONSUMER_SETTING_NAMES = array('custom_tc_profile_url', 'custom_system_setting_url');
87 private static $LTI_CONTEXT_SETTING_NAMES = array('custom_context_setting_url',
88 'custom_lineitems_url', 'custom_results_url',
89 'custom_context_memberships_url');
95 private static $LTI_RESOURCE_LINK_SETTING_NAMES = array('lis_result_sourcedid', 'lis_outcome_service_url',
96 'ext_ims_lis_basic_outcome_url', 'ext_ims_lis_resultvalue_sourcedids',
97 'ext_ims_lis_memberships_id', 'ext_ims_lis_memberships_url',
98 'ext_ims_lti_tool_setting', 'ext_ims_lti_tool_setting_id', 'ext_ims_lti_tool_setting_url',
99 'custom_link_setting_url',
100 'custom_lineitem_url', 'custom_result_url');
106 private static $CUSTOM_SUBSTITUTION_VARIABLES = array('User.id' => 'user_id',
107 'User.image' => 'user_image',
108 'User.username' => 'username',
109 'User.scope.mentor' => 'role_scope_mentor',
110 'Membership.role' => 'roles',
111 'Person.sourcedId' => 'lis_person_sourcedid',
112 'Person.name.full' => 'lis_person_name_full',
113 'Person.name.family' => 'lis_person_name_family',
114 'Person.name.given' => 'lis_person_name_given',
115 'Person.email.primary' => 'lis_person_contact_email_primary',
116 'Context.id' => 'context_id',
117 'Context.type' => 'context_type',
118 'Context.title' => 'context_title',
119 'Context.label' => 'context_label',
120 'CourseOffering.sourcedId' => 'lis_course_offering_sourcedid',
121 'CourseSection.sourcedId' => 'lis_course_section_sourcedid',
122 'CourseSection.label' => 'context_label',
123 'CourseSection.title' => 'context_title',
124 'ResourceLink.id' => 'resource_link_id',
125 'ResourceLink.title' => 'resource_link_title',
126 'ResourceLink.description' => 'resource_link_description',
127 'Result.sourcedId' => 'lis_result_sourcedid',
128 'BasicOutcome.url' => 'lis_outcome_service_url',
129 'ToolConsumerProfile.url' => 'custom_tc_profile_url',
130 'ToolProxy.url' => 'tool_proxy_url',
131 'ToolProxy.custom.url' => 'custom_system_setting_url',
132 'ToolProxyBinding.custom.url' => 'custom_context_setting_url',
133 'LtiLink.custom.url' => 'custom_link_setting_url',
134 'LineItems.url' => 'custom_lineitems_url',
135 'LineItem.url' => 'custom_lineitem_url',
136 'Results.url' => 'custom_results_url',
137 'Result.url' => 'custom_result_url',
138 'ToolProxyBinding.memberships.url' => 'custom_context_memberships_url');
139
140
146 public $ok = true;
152 public $consumer = null;
158 public $returnUrl = null;
164 public $user = null;
170 public $resourceLink = null;
176 public $context = null;
182 public $dataConnector = null;
188 public $defaultEmail = '';
200 public $allowSharing = false;
212 public $reason = null;
218 public $details = array();
224 public $baseUrl = null;
230 public $vendor = null;
236 public $product = null;
242 public $requiredServices = null;
248 public $optionalServices = null;
254 public $resourceHandlers = null;
255
261 protected $redirectUrl = null;
267 protected $mediaTypes = null;
273 protected $documentTargets = null;
279 protected $output = null;
285 protected $errorOutput = null;
291 protected $debugMode = false;
292
298 private $callbackHandler = null;
304 private $constraints = null;
305
312 {
313
314 $this->constraints = array();
315 $this->dataConnector = $dataConnector;
316 $this->ok = !is_null($this->dataConnector);
317
318// Set debug mode
319 $this->debugMode = isset($_POST['custom_debug']) && (strtolower($_POST['custom_debug']) === 'true');
320
321// Set return URL if available
322 if (isset($_POST['launch_presentation_return_url'])) {
323 $this->returnUrl = $_POST['launch_presentation_return_url'];
324 } else if (isset($_POST['content_item_return_url'])) {
325 $this->returnUrl = $_POST['content_item_return_url'];
326 }
327 $this->vendor = new Profile\Item();
328 $this->product = new Profile\Item();
329 $this->requiredServices = array();
330 $this->optionalServices = array();
331 $this->resourceHandlers = array();
332
333 }
334
338 public function handleRequest()
339 {
340
341 if ($this->ok) {
342 if ($this->authenticate()) {
343 $this->doCallback();
344 }
345 }
346 $this->result();
347
348 }
349
358 public function setParameterConstraint($name, $required = true, $maxLength = null, $messageTypes = null)
359 {
360
361 $name = trim($name);
362 if (strlen($name) > 0) {
363 $this->constraints[$name] = array('required' => $required, 'max_length' => $maxLength, 'messages' => $messageTypes);
364 }
365
366 }
367
373 public function getConsumers()
374 {
375
376 return $this->dataConnector->getToolConsumers();
377
378 }
379
388 public function findService($format, $methods)
389 {
390
391 $found = false;
392 $services = $this->consumer->profile->service_offered;
393 if (is_array($services)) {
394 $n = -1;
395 foreach ($services as $service) {
396 $n++;
397 if (!is_array($service->format) || !in_array($format, $service->format)) {
398 continue;
399 }
400 $missing = array();
401 foreach ($methods as $method) {
402 if (!is_array($service->action) || !in_array($method, $service->action)) {
403 $missing[] = $method;
404 }
405 }
406 $methods = $missing;
407 if (count($methods) <= 0) {
408 $found = $service;
409 break;
410 }
411 }
412 }
413
414 return $found;
415
416 }
417
423 public function doToolProxyService()
424 {
425
426// Create tool proxy
427 $toolProxyService = $this->findService('application/vnd.ims.lti.v2.toolproxy+json', array('POST'));
428 $secret = DataConnector::getRandomString(12);
429 $toolProxy = new MediaType\ToolProxy($this, $toolProxyService, $secret);
430 $http = $this->consumer->doServiceRequest($toolProxyService, 'POST', 'application/vnd.ims.lti.v2.toolproxy+json', json_encode($toolProxy));
431 $ok = $http->ok && ($http->status == 201) && isset($http->responseJson->tool_proxy_guid) && (strlen($http->responseJson->tool_proxy_guid) > 0);
432 if ($ok) {
433 $this->consumer->setKey($http->responseJson->tool_proxy_guid);
434 $this->consumer->secret = $toolProxy->security_contract->shared_secret;
435 $this->consumer->toolProxy = json_encode($toolProxy);
436 $this->consumer->save();
437 }
438
439 return $ok;
440
441 }
442
450 public static function parseRoles($roles)
451 {
452
453 if (!is_array($roles)) {
454 $roles = explode(',', $roles);
455 }
456 $parsedRoles = array();
457 foreach ($roles as $role) {
458 $role = trim($role);
459 if (!empty($role)) {
460 if (substr($role, 0, 4) !== 'urn:') {
461 $role = 'urn:lti:role:ims/lis/' . $role;
462 }
463 $parsedRoles[] = $role;
464 }
465 }
466
467 return $parsedRoles;
468
469 }
470
479 public static function sendForm($url, $params, $target = '')
480 {
481
482 $page = <<< EOD
483<html>
484<head>
485<title>IMS LTI message</title>
486<script type="text/javascript">
487//<![CDATA[
488function doOnLoad() {
489 document.forms[0].submit();
490}
491
492window.onload=doOnLoad;
493//]]>
494</script>
495</head>
496<body>
497<form action="{$url}" method="post" target="" encType="application/x-www-form-urlencoded">
498
499EOD;
500
501 foreach($params as $key => $value ) {
502 $key = htmlentities($key, ENT_COMPAT | ENT_HTML401, 'UTF-8');
503 $value = htmlentities($value, ENT_COMPAT | ENT_HTML401, 'UTF-8');
504 $page .= <<< EOD
505 <input type="hidden" name="{$key}" value="{$value}" />
506
507EOD;
508
509 }
510
511 $page .= <<< EOD
512</form>
513</body>
514</html>
515EOD;
516
517 return $page;
518
519 }
520
521###
522### PROTECTED METHODS
523###
524
530 protected function onLaunch()
531 {
532
533 $this->onError();
534
535 }
536
542 protected function onContentItem()
543 {
544
545 $this->onError();
546
547 }
548
554 protected function onRegister() {
555
556 $this->onError();
557
558 }
559
565 protected function onError()
566 {
567
568 $this->doCallback('onError');
569
570 }
571
572###
573### PRIVATE METHODS
574###
575
583 private function doCallback($method = null)
584 {
585
586 $callback = $method;
587 if (is_null($callback)) {
588 $callback = self::$METHOD_NAMES[$_POST['lti_message_type']];
589 }
590 if (method_exists($this, $callback)) {
591 $result = $this->$callback();
592 } else if (is_null($method) && $this->ok) {
593 $this->ok = false;
594 $this->reason = "Message type not supported: {$_POST['lti_message_type']}";
595 }
596 if ($this->ok && ($_POST['lti_message_type'] == 'ToolProxyRegistrationRequest')) {
597 $this->consumer->save();
598 }
599
600 }
601
609 private function result()
610 {
611
612 $ok = false;
613 if (!$this->ok) {
614 $ok = $this->onError();
615 }
616 if (!$ok) {
617 if (!$this->ok) {
618
619// If not valid, return an error message to the tool consumer if a return URL is provided
620 if (!empty($this->returnUrl)) {
621 $errorUrl = $this->returnUrl;
622 if (strpos($errorUrl, '?') === false) {
623 $errorUrl .= '?';
624 } else {
625 $errorUrl .= '&';
626 }
627 if ($this->debugMode && !is_null($this->reason)) {
628 $errorUrl .= 'lti_errormsg=' . urlencode("Debug error: $this->reason");
629 } else {
630 $errorUrl .= 'lti_errormsg=' . urlencode($this->message);
631 if (!is_null($this->reason)) {
632 $errorUrl .= '&lti_errorlog=' . urlencode("Debug error: $this->reason");
633 }
634 }
635 if (!is_null($this->consumer) && isset($_POST['lti_message_type']) && ($_POST['lti_message_type'] === 'ContentItemSelectionRequest')) {
636 $formParams = array();
637 if (isset($_POST['data'])) {
638 $formParams['data'] = $_POST['data'];
639 }
640 $version = (isset($_POST['lti_version'])) ? $_POST['lti_version'] : self::LTI_VERSION1;
641 $formParams = $this->consumer->signParameters($errorUrl, 'ContentItemSelection', $version, $formParams);
642 $page = self::sendForm($errorUrl, $formParams);
643 echo $page;
644 } else {
645 header("Location: {$errorUrl}");
646 }
647 exit;
648 } else {
649 if (!is_null($this->errorOutput)) {
651 } else if ($this->debugMode && !empty($this->reason)) {
652 echo "Debug error: {$this->reason}";
653 } else {
654 echo "Error: {$this->message}";
655 }
656 }
657 } else if (!is_null($this->redirectUrl)) {
658 header("Location: {$this->redirectUrl}");
659 exit;
660 } else if (!is_null($this->output)) {
661 echo $this->output;
662 }
663 }
664
665 }
666
674 private function authenticate()
675 {
676
677// Get the consumer
678 $doSaveConsumer = false;
679// Check all required launch parameters
680 $this->ok = isset($_POST['lti_message_type']) && array_key_exists($_POST['lti_message_type'], self::$MESSAGE_TYPES);
681 if (!$this->ok) {
682 $this->reason = 'Invalid or missing lti_message_type parameter.';
683 }
684 if ($this->ok) {
685 $this->ok = isset($_POST['lti_version']) && in_array($_POST['lti_version'], self::$LTI_VERSIONS);
686 if (!$this->ok) {
687 $this->reason = 'Invalid or missing lti_version parameter.';
688 }
689 }
690 if ($this->ok) {
691 if ($_POST['lti_message_type'] === 'basic-lti-launch-request') {
692 $this->ok = isset($_POST['resource_link_id']) && (strlen(trim($_POST['resource_link_id'])) > 0);
693 if (!$this->ok) {
694 $this->reason = 'Missing resource link ID.';
695 }
696 } else if ($_POST['lti_message_type'] === 'ContentItemSelectionRequest') {
697 if (isset($_POST['accept_media_types']) && (strlen(trim($_POST['accept_media_types'])) > 0)) {
698 $mediaTypes = array_filter(explode(',', str_replace(' ', '', $_POST['accept_media_types'])), 'strlen');
699 $mediaTypes = array_unique($mediaTypes);
700 $this->ok = count($mediaTypes) > 0;
701 if (!$this->ok) {
702 $this->reason = 'No accept_media_types found.';
703 } else {
704 $this->mediaTypes = $mediaTypes;
705 }
706 } else {
707 $this->ok = false;
708 }
709 if ($this->ok && isset($_POST['accept_presentation_document_targets']) && (strlen(trim($_POST['accept_presentation_document_targets'])) > 0)) {
710 $documentTargets = array_filter(explode(',', str_replace(' ', '', $_POST['accept_presentation_document_targets'])), 'strlen');
711 $documentTargets = array_unique($documentTargets);
712 $this->ok = count($documentTargets) > 0;
713 if (!$this->ok) {
714 $this->reason = 'Missing or empty accept_presentation_document_targets parameter.';
715 } else {
716 foreach ($documentTargets as $documentTarget) {
717 $this->ok = $this->checkValue($documentTarget, array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay', 'none'),
718 'Invalid value in accept_presentation_document_targets parameter: %s.');
719 if (!$this->ok) {
720 break;
721 }
722 }
723 if ($this->ok) {
724 $this->documentTargets = $documentTargets;
725 }
726 }
727 } else {
728 $this->ok = false;
729 }
730 if ($this->ok) {
731 $this->ok = isset($_POST['content_item_return_url']) && (strlen(trim($_POST['content_item_return_url'])) > 0);
732 if (!$this->ok) {
733 $this->reason = 'Missing content_item_return_url parameter.';
734 }
735 }
736 } else if ($_POST['lti_message_type'] == 'ToolProxyRegistrationRequest') {
737 $this->ok = ((isset($_POST['reg_key']) && (strlen(trim($_POST['reg_key'])) > 0)) &&
738 (isset($_POST['reg_password']) && (strlen(trim($_POST['reg_password'])) > 0)) &&
739 (isset($_POST['tc_profile_url']) && (strlen(trim($_POST['tc_profile_url'])) > 0)) &&
740 (isset($_POST['launch_presentation_return_url']) && (strlen(trim($_POST['launch_presentation_return_url'])) > 0)));
741 if ($this->debugMode && !$this->ok) {
742 $this->reason = 'Missing message parameters.';
743 }
744 }
745 }
746 $now = time();
747// Check consumer key
748 if ($this->ok && ($_POST['lti_message_type'] != 'ToolProxyRegistrationRequest')) {
749 $this->ok = isset($_POST['oauth_consumer_key']);
750 if (!$this->ok) {
751 $this->reason = 'Missing consumer key.';
752 }
753 if ($this->ok) {
754 $this->consumer = new ToolConsumer($_POST['oauth_consumer_key'], $this->dataConnector);
755 $this->ok = !is_null($this->consumer->created);
756 if (!$this->ok) {
757 $this->reason = 'Invalid consumer key.';
758 }
759 }
760 if ($this->ok) {
761 $today = date('Y-m-d', $now);
762 if (is_null($this->consumer->lastAccess)) {
763 $doSaveConsumer = true;
764 } else {
765 $last = date('Y-m-d', $this->consumer->lastAccess);
766 $doSaveConsumer = $doSaveConsumer || ($last !== $today);
767 }
768 $this->consumer->last_access = $now;
769 try {
770 $store = new OAuthDataStore($this);
773 $server->add_signature_method($method);
775 $res = $server->verify_request($request);
776 } catch (\Exception $e) {
777 $this->ok = false;
778 if (empty($this->reason)) {
779 if ($this->debugMode) {
780 $consumer = new OAuth\OAuthConsumer($this->consumer->getKey(), $this->consumer->secret);
781 $signature = $request->build_signature($method, $consumer, false);
782 $this->reason = $e->getMessage();
783 if (empty($this->reason)) {
784 $this->reason = 'OAuth exception';
785 }
786 $this->details[] = 'Timestamp: ' . time();
787 $this->details[] = "Signature: {$signature}";
788 $this->details[] = "Base string: {$request->base_string}]";
789 } else {
790 $this->reason = 'OAuth signature check failed - perhaps an incorrect secret or timestamp.';
791 }
792 }
793 }
794 }
795 if ($this->ok) {
796 $today = date('Y-m-d', $now);
797 if (is_null($this->consumer->lastAccess)) {
798 $doSaveConsumer = true;
799 } else {
800 $last = date('Y-m-d', $this->consumer->lastAccess);
801 $doSaveConsumer = $doSaveConsumer || ($last !== $today);
802 }
803 $this->consumer->last_access = $now;
804 if ($this->consumer->protected) {
805 if (!is_null($this->consumer->consumerGuid)) {
806 $this->ok = empty($_POST['tool_consumer_instance_guid']) ||
807 ($this->consumer->consumerGuid === $_POST['tool_consumer_instance_guid']);
808 if (!$this->ok) {
809 $this->reason = 'Request is from an invalid tool consumer.';
810 }
811 } else {
812 $this->ok = isset($_POST['tool_consumer_instance_guid']);
813 if (!$this->ok) {
814 $this->reason = 'A tool consumer GUID must be included in the launch request.';
815 }
816 }
817 }
818 if ($this->ok) {
819 $this->ok = $this->consumer->enabled;
820 if (!$this->ok) {
821 $this->reason = 'Tool consumer has not been enabled by the tool provider.';
822 }
823 }
824 if ($this->ok) {
825 $this->ok = is_null($this->consumer->enableFrom) || ($this->consumer->enableFrom <= $now);
826 if ($this->ok) {
827 $this->ok = is_null($this->consumer->enableUntil) || ($this->consumer->enableUntil > $now);
828 if (!$this->ok) {
829 $this->reason = 'Tool consumer access has expired.';
830 }
831 } else {
832 $this->reason = 'Tool consumer access is not yet available.';
833 }
834 }
835 }
836
837// Validate other message parameter values
838 if ($this->ok) {
839 if ($_POST['lti_message_type'] === 'ContentItemSelectionRequest') {
840 if (isset($_POST['accept_unsigned'])) {
841 $this->ok = $this->checkValue($_POST['accept_unsigned'], array('true', 'false'), 'Invalid value for accept_unsigned parameter: %s.');
842 }
843 if ($this->ok && isset($_POST['accept_multiple'])) {
844 $this->ok = $this->checkValue($_POST['accept_multiple'], array('true', 'false'), 'Invalid value for accept_multiple parameter: %s.');
845 }
846 if ($this->ok && isset($_POST['accept_copy_advice'])) {
847 $this->ok = $this->checkValue($_POST['accept_copy_advice'], array('true', 'false'), 'Invalid value for accept_copy_advice parameter: %s.');
848 }
849 if ($this->ok && isset($_POST['auto_create'])) {
850 $this->ok = $this->checkValue($_POST['auto_create'], array('true', 'false'), 'Invalid value for auto_create parameter: %s.');
851 }
852 if ($this->ok && isset($_POST['can_confirm'])) {
853 $this->ok = $this->checkValue($_POST['can_confirm'], array('true', 'false'), 'Invalid value for can_confirm parameter: %s.');
854 }
855 } else if (isset($_POST['launch_presentation_document_target'])) {
856 $this->ok = $this->checkValue($_POST['launch_presentation_document_target'], array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay'),
857 'Invalid value for launch_presentation_document_target parameter: %s.');
858 }
859 }
860 }
861
862 if ($this->ok && ($_POST['lti_message_type'] === 'ToolProxyRegistrationRequest')) {
863 $this->ok = $_POST['lti_version'] == self::LTI_VERSION2;
864 if (!$this->ok) {
865 $this->reason = 'Invalid lti_version parameter';
866 }
867 if ($this->ok) {
868 $http = new HTTPMessage($_POST['tc_profile_url'], 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json');
869 $this->ok = $http->send();
870 if (!$this->ok) {
871 $this->reason = 'Tool consumer profile not accessible.';
872 } else {
873 $tcProfile = json_decode($http->response);
874 $this->ok = !is_null($tcProfile);
875 if (!$this->ok) {
876 $this->reason = 'Invalid JSON in tool consumer profile.';
877 }
878 }
879 }
880// Check for required capabilities
881 if ($this->ok) {
882 $this->consumer = new ToolConsumer($_POST['reg_key'], $this->dataConnector);
883 $this->consumer->profile = $tcProfile;
884 $capabilities = $this->consumer->profile->capability_offered;
885 $missing = array();
886 foreach ($this->resourceHandlers as $resourceHandler) {
887 foreach ($resourceHandler->requiredMessages as $message) {
888 if (!in_array($message->type, $capabilities)) {
889 $missing[$message->type] = true;
890 }
891 }
892 }
893 foreach ($this->constraints as $name => $constraint) {
894 if ($constraint['required']) {
895 if (!in_array($name, $capabilities) && !in_array($name, array_flip($capabilities))) {
896 $missing[$name] = true;
897 }
898 }
899 }
900 if (!empty($missing)) {
901 ksort($missing);
902 $this->reason = 'Required capability not offered - \'' . implode('\', \'', array_keys($missing)) . '\'';
903 $this->ok = false;
904 }
905 }
906// Check for required services
907 if ($this->ok) {
908 foreach ($this->requiredServices as $service) {
909 foreach ($service->formats as $format) {
910 if (!$this->findService($format, $service->actions)) {
911 if ($this->ok) {
912 $this->reason = 'Required service(s) not offered - ';
913 $this->ok = false;
914 } else {
915 $this->reason .= ', ';
916 }
917 $this->reason .= "'{$format}' [" . implode(', ', $service->actions) . ']';
918 }
919 }
920 }
921 }
922 if ($this->ok) {
923 if ($_POST['lti_message_type'] === 'ToolProxyRegistrationRequest') {
924 $this->consumer->profile = $tcProfile;
925 $this->consumer->secret = $_POST['reg_password'];
926 $this->consumer->ltiVersion = $_POST['lti_version'];
927 $this->consumer->name = $tcProfile->product_instance->service_owner->service_owner_name->default_value;
928 $this->consumer->consumerName = $this->consumer->name;
929 $this->consumer->consumerVersion = "{$tcProfile->product_instance->product_info->product_family->code}-{$tcProfile->product_instance->product_info->product_version}";
930 $this->consumer->consumerGuid = $tcProfile->product_instance->guid;
931 $this->consumer->enabled = true;
932 $this->consumer->protected = true;
933 $doSaveConsumer = true;
934 }
935 }
936 } else if ($this->ok && !empty($_POST['custom_tc_profile_url']) && empty($this->consumer->profile)) {
937 $http = new HTTPMessage($_POST['custom_tc_profile_url'], 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json');
938 if ($http->send()) {
939 $tcProfile = json_decode($http->response);
940 if (!is_null($tcProfile)) {
941 $this->consumer->profile = $tcProfile;
942 $doSaveConsumer = true;
943 }
944 }
945 }
946
947// Validate message parameter constraints
948 if ($this->ok) {
949 $invalidParameters = array();
950 foreach ($this->constraints as $name => $constraint) {
951 if (empty($constraint['messages']) || in_array($_POST['lti_message_type'], $constraint['messages'])) {
952 $ok = true;
953 if ($constraint['required']) {
954 if (!isset($_POST[$name]) || (strlen(trim($_POST[$name])) <= 0)) {
955 $invalidParameters[] = "{$name} (missing)";
956 $ok = false;
957 }
958 }
959 if ($ok && !is_null($constraint['max_length']) && isset($_POST[$name])) {
960 if (strlen(trim($_POST[$name])) > $constraint['max_length']) {
961 $invalidParameters[] = "{$name} (too long)";
962 }
963 }
964 }
965 }
966 if (count($invalidParameters) > 0) {
967 $this->ok = false;
968 if (empty($this->reason)) {
969 $this->reason = 'Invalid parameter(s): ' . implode(', ', $invalidParameters) . '.';
970 }
971 }
972 }
973
974 if ($this->ok) {
975
976// Set the request context
977 if (isset($_POST['context_id'])) {
978 $this->context = Context::fromConsumer($this->consumer, trim($_POST['context_id']));
979 $title = '';
980 if (isset($_POST['context_title'])) {
981 $title = trim($_POST['context_title']);
982 }
983 if (empty($title)) {
984 $title = "Course {$this->context->getId()}";
985 }
986 $this->context->title = $title;
987 }
988
989// Set the request resource link
990 if (isset($_POST['resource_link_id'])) {
991 $contentItemId = '';
992 if (isset($_POST['custom_content_item_id'])) {
993 $contentItemId = $_POST['custom_content_item_id'];
994 }
995 $this->resourceLink = ResourceLink::fromConsumer($this->consumer, trim($_POST['resource_link_id']), $contentItemId);
996 if (!empty($this->context)) {
997 $this->resourceLink->setContextId($this->context->getRecordId());
998 }
999 $title = '';
1000 if (isset($_POST['resource_link_title'])) {
1001 $title = trim($_POST['resource_link_title']);
1002 }
1003 if (empty($title)) {
1004 $title = "Resource {$this->resourceLink->getId()}";
1005 }
1006 $this->resourceLink->title = $title;
1007// Delete any existing custom parameters
1008 foreach ($this->consumer->getSettings() as $name => $value) {
1009 if (strpos($name, 'custom_') === 0) {
1010 $this->consumer->setSetting($name);
1011 $doSaveConsumer = true;
1012 }
1013 }
1014 if (!empty($this->context)) {
1015 foreach ($this->context->getSettings() as $name => $value) {
1016 if (strpos($name, 'custom_') === 0) {
1017 $this->context->setSetting($name);
1018 }
1019 }
1020 }
1021 foreach ($this->resourceLink->getSettings() as $name => $value) {
1022 if (strpos($name, 'custom_') === 0) {
1023 $this->resourceLink->setSetting($name);
1024 }
1025 }
1026// Save LTI parameters
1027 foreach (self::$LTI_CONSUMER_SETTING_NAMES as $name) {
1028 if (isset($_POST[$name])) {
1029 $this->consumer->setSetting($name, $_POST[$name]);
1030 } else {
1031 $this->consumer->setSetting($name);
1032 }
1033 }
1034 if (!empty($this->context)) {
1035 foreach (self::$LTI_CONTEXT_SETTING_NAMES as $name) {
1036 if (isset($_POST[$name])) {
1037 $this->context->setSetting($name, $_POST[$name]);
1038 } else {
1039 $this->context->setSetting($name);
1040 }
1041 }
1042 }
1043 foreach (self::$LTI_RESOURCE_LINK_SETTING_NAMES as $name) {
1044 if (isset($_POST[$name])) {
1045 $this->resourceLink->setSetting($name, $_POST[$name]);
1046 } else {
1047 $this->resourceLink->setSetting($name);
1048 }
1049 }
1050// Save other custom parameters
1051 foreach ($_POST as $name => $value) {
1052 if ((strpos($name, 'custom_') === 0) &&
1053 !in_array($name, array_merge(self::$LTI_CONSUMER_SETTING_NAMES, self::$LTI_CONTEXT_SETTING_NAMES, self::$LTI_RESOURCE_LINK_SETTING_NAMES))) {
1054 $this->resourceLink->setSetting($name, $value);
1055 }
1056 }
1057 }
1058
1059// Set the user instance
1060 $userId = '';
1061 if (isset($_POST['user_id'])) {
1062 $userId = trim($_POST['user_id']);
1063 }
1064
1065 $this->user = User::fromResourceLink($this->resourceLink, $userId);
1066
1067// Set the user name
1068 $firstname = (isset($_POST['lis_person_name_given'])) ? $_POST['lis_person_name_given'] : '';
1069 $lastname = (isset($_POST['lis_person_name_family'])) ? $_POST['lis_person_name_family'] : '';
1070 $fullname = (isset($_POST['lis_person_name_full'])) ? $_POST['lis_person_name_full'] : '';
1071 $this->user->setNames($firstname, $lastname, $fullname);
1072
1073// Set the user email
1074 $email = (isset($_POST['lis_person_contact_email_primary'])) ? $_POST['lis_person_contact_email_primary'] : '';
1075 $this->user->setEmail($email, $this->defaultEmail);
1076
1077// Set the user image URI
1078 if (isset($_POST['user_image'])) {
1079 $this->user->image = $_POST['user_image'];
1080 }
1081
1082// Set the user roles
1083 if (isset($_POST['roles'])) {
1084 $this->user->roles = self::parseRoles($_POST['roles']);
1085 }
1086
1087// Initialise the consumer and check for changes
1088 $this->consumer->defaultEmail = $this->defaultEmail;
1089 if ($this->consumer->ltiVersion !== $_POST['lti_version']) {
1090 $this->consumer->ltiVersion = $_POST['lti_version'];
1091 $doSaveConsumer = true;
1092 }
1093 if (isset($_POST['tool_consumer_instance_name'])) {
1094 if ($this->consumer->consumerName !== $_POST['tool_consumer_instance_name']) {
1095 $this->consumer->consumerName = $_POST['tool_consumer_instance_name'];
1096 $doSaveConsumer = true;
1097 }
1098 }
1099 if (isset($_POST['tool_consumer_info_product_family_code'])) {
1100 $version = $_POST['tool_consumer_info_product_family_code'];
1101 if (isset($_POST['tool_consumer_info_version'])) {
1102 $version .= "-{$_POST['tool_consumer_info_version']}";
1103 }
1104// do not delete any existing consumer version if none is passed
1105 if ($this->consumer->consumerVersion !== $version) {
1106 $this->consumer->consumerVersion = $version;
1107 $doSaveConsumer = true;
1108 }
1109 } else if (isset($_POST['ext_lms']) && ($this->consumer->consumerName !== $_POST['ext_lms'])) {
1110 $this->consumer->consumerVersion = $_POST['ext_lms'];
1111 $doSaveConsumer = true;
1112 }
1113 if (isset($_POST['tool_consumer_instance_guid'])) {
1114 if (is_null($this->consumer->consumerGuid)) {
1115 $this->consumer->consumerGuid = $_POST['tool_consumer_instance_guid'];
1116 $doSaveConsumer = true;
1117 } else if (!$this->consumer->protected) {
1118 $doSaveConsumer = ($this->consumer->consumerGuid !== $_POST['tool_consumer_instance_guid']);
1119 if ($doSaveConsumer) {
1120 $this->consumer->consumerGuid = $_POST['tool_consumer_instance_guid'];
1121 }
1122 }
1123 }
1124 if (isset($_POST['launch_presentation_css_url'])) {
1125 if ($this->consumer->cssPath !== $_POST['launch_presentation_css_url']) {
1126 $this->consumer->cssPath = $_POST['launch_presentation_css_url'];
1127 $doSaveConsumer = true;
1128 }
1129 } else if (isset($_POST['ext_launch_presentation_css_url']) &&
1130 ($this->consumer->cssPath !== $_POST['ext_launch_presentation_css_url'])) {
1131 $this->consumer->cssPath = $_POST['ext_launch_presentation_css_url'];
1132 $doSaveConsumer = true;
1133 } else if (!empty($this->consumer->cssPath)) {
1134 $this->consumer->cssPath = null;
1135 $doSaveConsumer = true;
1136 }
1137 }
1138
1139// Persist changes to consumer
1140 if ($doSaveConsumer) {
1141 $this->consumer->save();
1142 }
1143 if ($this->ok && isset($this->context)) {
1144 $this->context->save();
1145 }
1146 if ($this->ok && isset($this->resourceLink)) {
1147
1148// Check if a share arrangement is in place for this resource link
1149 $this->ok = $this->checkForShare();
1150
1151// Persist changes to resource link
1152 $this->resourceLink->save();
1153
1154// Save the user instance
1155 if (isset($_POST['lis_result_sourcedid'])) {
1156 if ($this->user->ltiResultSourcedId !== $_POST['lis_result_sourcedid']) {
1157 $this->user->ltiResultSourcedId = $_POST['lis_result_sourcedid'];
1158 $this->user->save();
1159 }
1160 } else if (!empty($this->user->ltiResultSourcedId)) {
1161 $this->user->ltiResultSourcedId = '';
1162 $this->user->save();
1163 }
1164 }
1165
1166 return $this->ok;
1167
1168 }
1169
1175 private function checkForShare()
1176 {
1177
1178 $ok = true;
1179 $doSaveResourceLink = true;
1180
1181 $id = $this->resourceLink->primaryResourceLinkId;
1182
1183 $shareRequest = isset($_POST['custom_share_key']) && !empty($_POST['custom_share_key']);
1184 if ($shareRequest) {
1185 if (!$this->allowSharing) {
1186 $ok = false;
1187 $this->reason = 'Your sharing request has been refused because sharing is not being permitted.';
1188 } else {
1189// Check if this is a new share key
1190 $shareKey = new ResourceLinkShareKey($this->resourceLink, $_POST['custom_share_key']);
1191 if (!is_null($shareKey->primaryConsumerKey) && !is_null($shareKey->primaryResourceLinkId)) {
1192// Update resource link with sharing primary resource link details
1193 $key = $shareKey->primaryConsumerKey;
1194 $id = $shareKey->primaryResourceLinkId;
1195 $ok = ($key !== $this->consumer->getKey()) || ($id != $this->resourceLink->getId());
1196 if ($ok) {
1197 $this->resourceLink->primaryConsumerKey = $key;
1198 $this->resourceLink->primaryResourceLinkId = $id;
1199 $this->resourceLink->shareApproved = $shareKey->autoApprove;
1200 $ok = $this->resourceLink->save();
1201 if ($ok) {
1202 $doSaveResourceLink = false;
1203 $this->user->getResourceLink()->primaryConsumerKey = $key;
1204 $this->user->getResourceLink()->primaryResourceLinkId = $id;
1205 $this->user->getResourceLink()->shareApproved = $shareKey->autoApprove;
1206 $this->user->getResourceLink()->updated = time();
1207// Remove share key
1208 $shareKey->delete();
1209 } else {
1210 $this->reason = 'An error occurred initialising your share arrangement.';
1211 }
1212 } else {
1213 $this->reason = 'It is not possible to share your resource link with yourself.';
1214 }
1215 }
1216 if ($ok) {
1217 $ok = !is_null($key);
1218 if (!$ok) {
1219 $this->reason = 'You have requested to share a resource link but none is available.';
1220 } else {
1221 $ok = (!is_null($this->user->getResourceLink()->shareApproved) && $this->user->getResourceLink()->shareApproved);
1222 if (!$ok) {
1223 $this->reason = 'Your share request is waiting to be approved.';
1224 }
1225 }
1226 }
1227 }
1228 } else {
1229// Check no share is in place
1230 $ok = is_null($id);
1231 if (!$ok) {
1232 $this->reason = 'You have not requested to share a resource link but an arrangement is currently in place.';
1233 }
1234 }
1235
1236// Look up primary resource link
1237 if ($ok && !is_null($id)) {
1238 $consumer = new ToolConsumer($key, $this->dataConnector);
1239 $ok = !is_null($consumer->created);
1240 if ($ok) {
1242 $ok = !is_null($resourceLink->created);
1243 }
1244 if ($ok) {
1245 if ($doSaveResourceLink) {
1246 $this->resourceLink->save();
1247 }
1248 $this->resourceLink = $resourceLink;
1249 } else {
1250 $this->reason = 'Unable to load resource link being shared.';
1251 }
1252 }
1253
1254 return $ok;
1255
1256 }
1257
1263 private function checkValue($value, $values, $reason)
1264 {
1265
1266 $ok = in_array($value, $values);
1267 if (!$ok && !empty($reason)) {
1268 $this->reason = sprintf($reason, $value);
1269 }
1270
1271 return $ok;
1272
1273 }
1274
1275}
$result
user()
Definition: user.php:4
html()
$n
Definition: RandomTest.php:85
foreach($paths as $path) $request
Definition: asyncclient.php:32
exit
Definition: backend.php:16
$version
Definition: build.php:27
$_POST["username"]
An exception for terminatinating execution or to throw for unit testing.
Class to represent an HTTP message.
Definition: HTTPMessage.php:15
Class to represent an OAuth Consumer.
Class to represent an OAuth Data Store.
static from_request($http_method=null, $http_url=null, $parameters=null)
attempt to build up a request from what was passed to the server
Class to represent an OAuth Server.
Definition: OAuthServer.php:12
Class to represent an OAuth HMAC_SHA1 signature method.
Class to represent a generic item object.
Definition: Item.php:15
static fromConsumer($consumer, $ltiContextId)
Class constructor from consumer.
Definition: Context.php:425
Class to provide a connection to a persistent store for LTI objects.
static getRandomString($length=8)
Generate a random string.
Class to represent an LTI Tool Proxy media type.
Definition: ToolProxy.php:18
Class to represent a tool consumer resource link share key.
Class to represent a tool consumer.
Class to represent an LTI Tool Provider.
$optionalServices
Optional services used by Tool Provider.
$resourceHandlers
Resource handlers for Tool Provider.
$ok
True if the last request was successful.
$returnUrl
Return URL provided by tool consumer.
$documentTargets
URL to redirect user to on successful completion of the request.
onRegister()
Process a valid tool proxy registration request.
static sendForm($url, $params, $target='')
Generate a web page containing an auto-submitted form of parameters.
static $CUSTOM_SUBSTITUTION_VARIABLES
Names of LTI custom parameter substitution variables (or capabilities) and their associated default m...
static $LTI_CONSUMER_SETTING_NAMES
Names of LTI parameters to be retained in the consumer settings property.
const ID_SCOPE_SEPARATOR
Character used to separate each element of an ID.
static parseRoles($roles)
Get an array of fully qualified user roles.
const LTI_VERSION2
LTI version 2 for messages.
doToolProxyService()
Send the tool proxy to the Tool Consumer.
$details
Details for error message relating to last request processed.
setParameterConstraint($name, $required=true, $maxLength=null, $messageTypes=null)
Add a parameter constraint to be checked on launch.
checkValue($value, $values, $reason)
Validate a parameter value from an array of permitted values.
static $LTI_RESOURCE_LINK_SETTING_NAMES
Names of LTI parameters to be retained in the resource link settings property.
doCallback($method=null)
Call any callback function for the requested action.
getConsumers()
Get an array of defined tool consumers.
const ID_SCOPE_ID_ONLY
Use ID value only.
static $LTI_CONTEXT_SETTING_NAMES
Names of LTI parameters to be retained in the context settings property.
const CONNECTION_ERROR_MESSAGE
Default connection error message.
findService($format, $methods)
Find an offered service based on a media type and HTTP action(s)
$requiredServices
Services required by Tool Provider.
$redirectUrl
URL to redirect user to on successful completion of the request.
const ID_SCOPE_CONTEXT
Prefix the ID with the consumer key and context ID.
$message
Message for last request processed.
$errorOutput
HTML to be displayed on an unsuccessful completion of the request and no return URL is available.
static $METHOD_NAMES
List of supported message types and associated class methods.
const ID_SCOPE_GLOBAL
Prefix an ID with the consumer key.
const ID_SCOPE_RESOURCE
Prefix the ID with the consumer key and resource ID.
$constraints
LTI parameter constraints for auto validation checks.
$reason
Error message for last request processed.
$idScope
Scope to use for user IDs.
__construct($dataConnector)
Class constructor.
static $LTI_VERSIONS
Permitted LTI versions for messages.
$baseUrl
Base URL for tool provider service.
result()
Perform the result of an action.
$output
HTML to be displayed on a successful completion of the request.
const LTI_VERSION1
LTI version 1 for messages.
onContentItem()
Process a valid content-item request.
handleRequest()
Process an incoming request.
$callbackHandler
Callback functions for handling requests.
static $MESSAGE_TYPES
List of supported message types and associated class methods.
onLaunch()
Process a valid launch request.
$mediaTypes
URL to redirect user to on successful completion of the request.
$debugMode
Whether debug messages explaining the cause of errors are to be returned to the tool consumer.
authenticate()
Check the authenticity of the LTI launch request.
$allowSharing
Whether shared resource link arrangements are permitted.
onError()
Process a response to an invalid request.
checkForShare()
Check if a share arrangement is in place.
static fromResourceLink($resourceLink, $ltiUserId)
Class constructor from resource link.
Definition: User.php:413
$key
Definition: croninfo.php:18
if(!array_key_exists('StateId', $_REQUEST)) $id
if(! $oauthconfig->getBoolean('getUserInfo.enable', FALSE)) $store
Definition: getUserInfo.php:11
input
Definition: langcheck.php:166
$format
Definition: metadata.php:141
if( $orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:201
$target
Definition: test.php:19
$url
$http
Definition: raiseError.php:7
$server
Definition: sabredav.php:48
foreach($_POST as $key=> $value) $res
$values