ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
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';
40  const ID_SCOPE_ID_ONLY = 0;
44  const ID_SCOPE_GLOBAL = 1;
48  const ID_SCOPE_CONTEXT = 2;
52  const ID_SCOPE_RESOURCE = 3;
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 = '';
194  public $idScope = self::ID_SCOPE_ID_ONLY;
200  public $allowSharing = false;
206  public $message = self::CONNECTION_ERROR_MESSAGE;
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'));
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[
488 function doOnLoad() {
489  document.forms[0].submit();
490 }
491 
492 window.onload=doOnLoad;
493 //]]>
494 </script>
495 </head>
496 <body>
497 <form action="{$url}" method="post" target="" encType="application/x-www-form-urlencoded">
498 
499 EOD;
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 
507 EOD;
508 
509  }
510 
511  $page .= <<< EOD
512 </form>
513 </body>
514 </html>
515 EOD;
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)) {
650  echo $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);
772  $method = new OAuth\OAuthSignatureMethod_HMAC_SHA1();
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 }
$params
Definition: disable.php:11
if($orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:193
static parseRoles($roles)
Get an array of fully qualified user roles.
getConsumers()
Get an array of defined tool consumers.
$output
HTML to be displayed on a successful completion of the request.
$format
Definition: metadata.php:141
Class to represent an OAuth Data Store.
const LTI_VERSION2
LTI version 2 for messages.
$result
const ID_SCOPE_RESOURCE
Prefix the ID with the consumer key and resource ID.
$secret
Definition: demo.php:27
$callbackHandler
Callback functions for handling requests.
$reason
Error message for last request processed.
Class to represent an LTI Tool Provider.
$errorOutput
HTML to be displayed on an unsuccessful completion of the request and no return URL is available...
__construct($dataConnector)
Class constructor.
static $METHOD_NAMES
List of supported message types and associated class methods.
$baseUrl
Base URL for tool provider service.
if(!array_key_exists('StateId', $_REQUEST)) $id
$idScope
Scope to use for user IDs.
$documentTargets
URL to redirect user to on successful completion of the request.
authenticate()
Check the authenticity of the LTI launch request.
Class to represent a tool consumer.
$constraints
LTI parameter constraints for auto validation checks.
$service
Definition: login.php:15
onLaunch()
Process a valid launch request.
static fromResourceLink($resourceLink, $ltiUserId)
Class constructor from resource link.
Definition: User.php:413
checkValue($value, $values, $reason)
Validate a parameter value from an array of permitted values.
if(! $oauthconfig->getBoolean('getUserInfo.enable', FALSE)) $store
Definition: getUserInfo.php:11
user()
Definition: user.php:4
Class to represent a generic item object.
Definition: Item.php:14
static $MESSAGE_TYPES
List of supported message types and associated class methods.
static from_request($http_method=null, $http_url=null, $parameters=null)
attempt to build up a request from what was passed to the server
if($format !==null) $name
Definition: metadata.php:146
onContentItem()
Process a valid content-item request.
findService($format, $methods)
Find an offered service based on a media type and HTTP action(s)
$message
Message for last request processed.
foreach($_POST as $key=> $value) $res
setParameterConstraint($name, $required=true, $maxLength=null, $messageTypes=null)
Add a parameter constraint to be checked on launch.
onError()
Process a response to an invalid request.
const CONNECTION_ERROR_MESSAGE
Default connection error message.
static sendForm($url, $params, $target='')
Generate a web page containing an auto-submitted form of parameters.
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
Class to represent an OAuth Consumer.
const ID_SCOPE_CONTEXT
Prefix the ID with the consumer key and context ID.
$redirectUrl
URL to redirect user to on successful completion of the request.
Add a drawing to the header
Definition: 04printing.php:69
$n
Definition: RandomTest.php:85
Class to represent a tool consumer resource link share key.
$http
Definition: raiseError.php:7
$resourceHandlers
Resource handlers for Tool Provider.
EOD
Definition: example_053.php:93
Create styles array
The data for the language used.
$allowSharing
Whether shared resource link arrangements are permitted.
$debugMode
Whether debug messages explaining the cause of errors are to be returned to the tool consumer...
$server
Definition: getUserInfo.php:12
result()
Perform the result of an action.
$requiredServices
Services required by Tool Provider.
static getRandomString($length=8)
Generate a random string.
const LTI_VERSION1
LTI version 1 for messages.
html()
doCallback($method=null)
Call any callback function for the requested action.
static $LTI_CONTEXT_SETTING_NAMES
Names of LTI parameters to be retained in the context settings property.
static $LTI_RESOURCE_LINK_SETTING_NAMES
Names of LTI parameters to be retained in the resource link settings property.
doToolProxyService()
Send the tool proxy to the Tool Consumer.
const ID_SCOPE_SEPARATOR
Character used to separate each element of an ID.
$url
handleRequest()
Process an incoming request.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
Class to represent an OAuth Server.
Definition: OAuthServer.php:12
$returnUrl
Return URL provided by tool consumer.
$details
Details for error message relating to last request processed.
$mediaTypes
URL to redirect user to on successful completion of the request.
checkForShare()
Check if a share arrangement is in place.
Class to represent an LTI Tool Proxy media type.
Definition: ToolProxy.php:17
$key
Definition: croninfo.php:18
static $CUSTOM_SUBSTITUTION_VARIABLES
Names of LTI custom parameter substitution variables (or capabilities) and their associated default m...
$_POST["username"]
static $LTI_VERSIONS
Permitted LTI versions for messages.
const ID_SCOPE_GLOBAL
Prefix an ID with the consumer key.
onRegister()
Process a valid tool proxy registration request.
$optionalServices
Optional services used by Tool Provider.
Class to represent an HTTP message.
Definition: HTTPMessage.php:14
Class to represent an OAuth HMAC_SHA1 signature method.
$ok
True if the last request was successful.
static $LTI_CONSUMER_SETTING_NAMES
Names of LTI parameters to be retained in the consumer settings property.
static fromConsumer($consumer, $ltiContextId)
Class constructor from consumer.
Definition: Context.php:425
const ID_SCOPE_ID_ONLY
Use ID value only.