ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
ResourceLink.php
Go to the documentation of this file.
1 <?php
2 
19 namespace ILIAS\LTI\ToolProvider;
20 
25 use DOMDocument;
26 use DOMElement;
27 
36 {
37  use ApiHook;
38 
42  public const EXT_READ = 1;
43 
47  public const EXT_WRITE = 2;
48 
52  public const EXT_DELETE = 3;
53 
57  public const EXT_CREATE = 4;
58 
62  public const EXT_UPDATE = 5;
63 
67  public const EXT_TYPE_DECIMAL = 'decimal';
68 
72  public const EXT_TYPE_PERCENTAGE = 'percentage';
73 
77  public const EXT_TYPE_RATIO = 'ratio';
78 
82  public const EXT_TYPE_LETTER_AF = 'letteraf';
83 
87  public const EXT_TYPE_LETTER_AF_PLUS = 'letterafplus';
88 
92  public const EXT_TYPE_PASS_FAIL = 'passfail';
93 
97  public const EXT_TYPE_TEXT = 'freetext';
98 
104  public ?string $title = null;
105 
111  public ?string $ltiResourceLinkId = null;
112 
126  public ?array $groupSets = null;
127 
138  public ?array $groups = null;
139 
145  public ?HTTPMessage $lastServiceRequest = null;
146 
152  public ?string $extRequest = null;
153 
159  public $extRequestHeaders = null;
160 
166  public ?string $extResponse = null;
167 
173  public $extResponseHeaders = null;
174 
180  public ?string $primaryResourceLinkId = null;
181 
187  public ?bool $shareApproved = null;
188 
194  public ?int $created = null;
195 
201  public ?int $updated = null;
202 
208  private ?int $id = null;
209 
215  private ?Platform $platform = null;
216 
222  private ?int $platformId = null;
223 
229  private ?Context $context = null;
230 
236  private ?int $contextId = null;
237 
243  private ?array $settings = null;
244 
250  private bool $settingsChanged = false;
251 
257  private ?DOMDocument $extDoc = null;
258 
264  private ?array $extNodes = null;
265 
271  private ?DataConnector $dataConnector = null;
272 
276  public function __construct()
277  {
278  $this->initialize();
279  }
280 
284  public function initialize()
285  {
286  $this->title = '';
287  $this->settings = array();
288  $this->groupSets = null;
289  $this->groups = null;
290  $this->primaryResourceLinkId = null;
291  $this->shareApproved = null;
292  $this->created = null;
293  $this->updated = null;
294  }
295 
301  public function initialise()
302  {
303  $this->initialize();
304  }
305 
311  public function save(): bool
312  {
313  $ok = $this->getDataConnector()->saveResourceLink($this);
314  if ($ok) {
315  $this->settingsChanged = false;
316  }
317 
318  return $ok;
319  }
320 
326  public function delete(): bool
327  {
328  return $this->getDataConnector()->deleteResourceLink($this);
329  }
330 
331  // /**
332  // * Get tool consumer.
333  // *
334  // * @deprecated Use getPlatform() instead
335  // * @see Context::getPlatform()
336  // *
337  // * @return ToolConsumer Tool consumer object for this resource link.
338  // */
339  // public function getConsumer()
340  // {
341  // Util::logDebug('Method ceLTIc\LTI\ResourceLink::getConsumer() has been deprecated; please use ceLTIc\LTI\ResourceLink::getPlatform() instead.',
342  // true);
343  // return $this->getPlatform();
344  // }
345 
346  // /**
347  // * Get tool consumer ID.
348  // *
349  // * @deprecated Use getPlatformId() instead
350  // * @see Context::getPlatformId()
351  // *
352  // * @return int|null Tool Consumer ID for this resource link.
353  // */
354  // public function getConsumerId()
355  // {
356  // Util::logDebug('Method ceLTIc\LTI\ResourceLink::getConsumerId() has been deprecated; please use ceLTIc\LTI\ResourceLink::getPlatformId() instead.',
357  // true);
358  // return $this->getPlatformId();
359  // }
360 
361  // /**
362  // * Set tool consumer ID.
363  // *
364  // * @deprecated Use setPlatformId() instead
365  // * @see Context::setPlatformId()
366  // *
367  // * @param int $consumerId Tool Consumer ID for this resource link.
368  // */
369  // public function setConsumerId($consumerId)
370  // {
371  // Util::logDebug('Method ceLTIc\LTI\ResourceLink::setConsumerId() has been deprecated; please use ceLTIc\LTI\ResourceLink::setPlatformId() instead.',
372  // true);
373  // $this->setPlatformId($consumerId);
374  // }
375 
381  public function getPlatform(): ?Platform
382  {
383  if (is_null($this->platform)) {
384  if (!is_null($this->context) || !is_null($this->contextId)) {
385  $this->platform = $this->getContext()->getPlatform();
386  } else {
387  //UK changed from $this->platform = Platform::fromRecordId($this->platformId, $this->getDataConnector());
388  $this->platform = \ilLTIPlatform::fromRecordId($this->platformId, $this->getDataConnector());
389  }
390  }
391 
392  return $this->platform;
393  }
394 
400  public function getPlatformId(): ?int
401  {
402  return $this->platformId;
403  }
404 
409  public function setPlatformId(?int $platformId)
410  {
411  $this->platform = null;
412  $this->platformId = $platformId;
413  }
414 
420  public function getContext(): ?Context
421  {
422  if (is_null($this->context) && !is_null($this->contextId)) {
423  $this->context = Context::fromRecordId($this->contextId, $this->getDataConnector());
424  }
425 
426  return $this->context;
427  }
428 
434  public function getContextId(): ?int
435  {
436  if (is_null($this->contextId) && !is_null($this->context)) {
437  $this->contextId = $this->context->getRecordId();
438  }
439 
440  return $this->contextId;
441  }
442 
447  public function setContext(Context $context)
448  {
449  $this->context = $context;
450  $this->contextId = $context->getRecordId();
451  }
452 
457  public function setContextId(?int $contextId)
458  {
459  if ($this->contextId !== $contextId) {
460  $this->context = null;
461  $this->contextId = $contextId;
462  }
463  }
464 
470  public function getKey(): string
471  {
472  return $this->getPlatform()->getKey();
473  }
474 
480  public function getId(): ?string
481  {
483  }
484 
490  public function getRecordId(): ?int
491  {
492  return $this->id;
493  }
494 
499  public function setRecordId(int $id)
500  {
501  $this->id = $id;
502  }
503 
509  public function getDataConnector(): ?DataConnector
510  {
511  if (empty($this->dataConnector)) {
512  $this->getPlatform();
513  if (!empty($this->platform)) {
514  $this->dataConnector = $this->platform->getDataConnector();
515  }
516  }
517 
518  return $this->dataConnector;
519  }
520 
527  public function getSetting(string $name, string $default = ''): string
528  {
529  if (array_key_exists($name, $this->settings)) {
530  $value = $this->settings[$name];
531  } else {
532  $value = $default;
533  }
534 
535  return $value;
536  }
537 
543  public function setSetting(string $name, string $value = null)
544  {
545  $old_value = $this->getSetting($name);
546  if ($value !== $old_value) {
547  if (!empty($value)) {
548  $this->settings[$name] = $value;
549  } else {
550  unset($this->settings[$name]);
551  }
552  $this->settingsChanged = true;
553  }
554  }
555 
561  public function getSettings(): ?array
562  {
563  return $this->settings;
564  }
565 
570  public function setSettings(array $settings)
571  {
572  $this->settings = $settings;
573  }
574 
580  public function saveSettings(): bool
581  {
582  if ($this->settingsChanged) {
583  $ok = $this->save();
584  } else {
585  $ok = true;
586  }
587 
588  return $ok;
589  }
590 
596  public function hasOutcomesService(): bool
597  {
598  $has = !empty($this->getSetting('ext_ims_lis_basic_outcome_url')) || !empty($this->getSetting('lis_outcome_service_url'));
599  if (!$has && !empty($this->getSetting('custom_lineitem_url')) && !empty($this->getSetting('custom_ags_scopes'))) {
600  $scopes = explode(',', $this->getSetting('custom_ags_scopes'));
601  $has = in_array(Service\Score::$SCOPE, $scopes) && in_array(Service\Result::$SCOPE, $scopes);
602  }
603  if (!$has) {
604  $has = self::hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this);
605  }
606  return $has;
607  }
608 
614  public function hasMembershipsService(): bool
615  {
616  $has = false;
617  if (!empty($this->getContextId())) {
618  $has = !empty($this->getContext()->getSetting('custom_context_memberships_url')) || !empty($this->getContext()->getSetting('custom_context_memberships_v2_url'));
619  }
620  if (!$has) {
621  $has = !empty($this->getSetting('custom_link_memberships_url'));
622  }
623  if (!$has) {
624  $has = !empty($this->getSetting('ext_ims_lis_memberships_url'));
625  }
626  if (!$has) {
627  $has = self::hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this);
628  }
629 
630  return $has;
631  }
632 
638  public function hasSettingService(): bool
639  {
640  $url = $this->getSetting('ext_ims_lti_tool_setting_url');
641 
642  return !empty($url);
643  }
644 
650  public function hasLineItemService(): bool
651  {
652  $has = false;
653  if (!empty($this->getSetting('custom_ags_scopes'))) {
654  $scopes = explode(',', $this->getSetting('custom_ags_scopes'));
655  if (in_array(Service\LineItem::$SCOPE, $scopes) || in_array(Service\LineItem::$SCOPE_READONLY, $scopes)) {
656  $has = !empty($this->getSetting('custom_lineitems_url'));
657  }
658  }
659 
660  return $has;
661  }
662 
668  public function hasScoreService(): bool
669  {
670  $has = false;
671  if (!empty($this->getSetting('custom_ags_scopes'))) {
672  $scopes = explode(',', $this->getSetting('custom_ags_scopes'));
673  if (in_array(Service\Score::$SCOPE, $scopes)) {
674  $has = !empty($this->getSetting('custom_lineitem_url'));
675  }
676  }
677 
678  return $has;
679  }
680 
686  public function hasResultService(): bool
687  {
688  $has = false;
689  if (!empty($this->getSetting('custom_ags_scopes'))) {
690  $scopes = explode(',', $this->getSetting('custom_ags_scopes'));
691  if (in_array(Service\Result::$SCOPE, $scopes)) {
692  $has = !empty($this->getSetting('custom_lineitem_url'));
693  }
694  }
695 
696  return $has;
697  }
698 
704  public function hasAssessmentControlService(): bool
705  {
706  $url = $this->getSetting('custom_ap_acs_url');
707 
708  return !empty($url);
709  }
710 
718  public function doOutcomesService(int $action, Outcome $ltiOutcome, UserResult $userResult): bool
719  {
720  $ok = false;
721  $this->extResponse = '';
722  // Lookup service details from the source resource link appropriate to the user (in case the destination is being shared)
723  $sourceResourceLink = $userResult->getResourceLink();
724  $sourcedId = $userResult->ltiResultSourcedId;
725 
726  // Use LTI 1.1 service in preference to extension service if it is available
727  $urlAGS = $sourceResourceLink->getSetting('custom_lineitem_url');
728  $urlLTI11 = $sourceResourceLink->getSetting('lis_outcome_service_url');
729  $urlExt = $sourceResourceLink->getSetting('ext_ims_lis_basic_outcome_url');
730 
731  if (!empty($urlAGS)) {
732  if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL) && $sourceResourceLink->hasResultService()) {
733  $ltiOutcome = $this->doResultService($userResult, $urlAGS);
734  $ok = !empty($ltiOutcome);
735  } elseif ((($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL)) && $sourceResourceLink->hasScoreService()) ||
736  ($action === self::EXT_DELETE)) {
737  if ($action === self::EXT_DELETE) {
738  $ltiOutcome->setValue(null);
739  $ltiOutcome->activityProgress = 'Initialized';
740  $ltiOutcome->gradingProgress = 'NotReady';
741  }
742  $ok = $this->doScoreService($ltiOutcome, $userResult, $urlAGS);
743  }
744  }
745  if (!$ok && is_null($ltiOutcome->getValue())) {
746  $ltiOutcome->setValue('');
747  }
748  if (!$ok && !empty($urlLTI11)) {
749  $do = '';
750  $outcome = $ltiOutcome->getValue();
751  if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
752  $do = 'readResult';
753  } elseif (($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
754  $do = 'replaceResult';
755  if (($ltiOutcome->getPointsPossible() <> 1) && ($ltiOutcome->getPointsPossible() > 0)) {
756  $outcome = $outcome / $ltiOutcome->getPointsPossible();
757  }
758  } elseif ($action === self::EXT_DELETE) {
759  $do = 'deleteResult';
760  }
761  if (!empty($do)) {
762  $xml = '';
763  if ($action === self::EXT_WRITE) {
764  $comment = (empty($ltiOutcome->comment)) ? '' : trim($ltiOutcome->comment);
765  if (!empty($comment) && !empty($sourceResourceLink->getSetting('ext_outcome_data_values_accepted'))) {
766  $resultDataTypes = explode(',', $sourceResourceLink->getSetting('ext_outcome_data_values_accepted'));
767  $resultDataType = '';
768  if (count($resultDataTypes) === 1) {
769  $resultDataType = $resultDataTypes[0];
770  } elseif (count($resultDataTypes) > 1) {
771  $isUrl = (strpos($comment, 'http://') === 0) || (strpos($comment, 'https://') === 0);
772  if ($isUrl && in_array('ltiLaunchUrl', $resultDataTypes)) {
773  $resultDataType = 'ltiLaunchUrl';
774  } elseif ($isUrl && in_array('url', $resultDataTypes)) {
775  $resultDataType = 'url';
776  } elseif (in_array('text', $resultDataTypes)) {
777  $resultDataType = 'text';
778  }
779  }
780  if (!empty($resultDataType)) {
781  $xml = <<< EOF
782 
783  <resultData>
784  <{$resultDataType}>{$comment}</{$resultDataType}>
785  </resultData>
786 EOF;
787  }
788  }
789  $xml = <<< EOF
790 
791  <result>
792  <resultScore>
793  <language>{$ltiOutcome->language}</language>
794  <textString>{$outcome}</textString>
795  </resultScore>{$xml}
796  </result>
797 EOF;
798  }
799  $sourcedId = htmlentities($sourcedId);
800  $xml = <<<EOF
801  <resultRecord>
802  <sourcedGUID>
803  <sourcedId>{$sourcedId}</sourcedId>
804  </sourcedGUID>{$xml}
805  </resultRecord>
806 EOF;
807  if ($this->doLTI11Service($do, $urlLTI11, $xml)) {
808  switch ($action) {
809  case self::EXT_READ:
810  if (!isset($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString'])) {
811  break;
812  } else {
813  $ltiOutcome->setValue($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString']);
814  }
815  // no break
816  case self::EXT_WRITE:
817  case self::EXT_DELETE:
818  $ok = true;
819  break;
820  }
821  }
822  }
823  }
824  if (!$ok && !empty($urlExt)) {
825  $do = '';
826  $outcome = $ltiOutcome->getValue();
827  if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
828  $do = 'basic-lis-readresult';
829  } elseif (($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
830  $do = 'basic-lis-updateresult';
831  if (($ltiOutcome->getPointsPossible() <> 1) && ($ltiOutcome->getPointsPossible() > 0)) {
832  $outcome = $outcome / $ltiOutcome->getPointsPossible();
833  }
834  } elseif ($action === self::EXT_DELETE) {
835  $do = 'basic-lis-deleteresult';
836  }
837  if (!empty($do)) {
838  $params = array();
839  $params['sourcedid'] = $sourcedId;
840  $params['result_resultscore_textstring'] = $outcome;
841  if (!empty($ltiOutcome->language)) {
842  $params['result_resultscore_language'] = $ltiOutcome->language;
843  }
844  if (!empty($ltiOutcome->status)) {
845  $params['result_statusofresult'] = $ltiOutcome->status;
846  }
847  if (!empty($ltiOutcome->date)) {
848  $params['result_date'] = $ltiOutcome->date;
849  }
850  if (!empty($ltiOutcome->type)) {
851  $params['result_resultvaluesourcedid'] = $ltiOutcome->type;
852  }
853  if (!empty($ltiOutcome->dataSource)) {
854  $params['result_datasource'] = $ltiOutcome->dataSource;
855  }
856  if ($this->doService($do, $urlExt, $params, 'https://purl.imsglobal.org/spec/lti-ext/scope/outcomes')) {
857  switch ($action) {
858  case self::EXT_READ:
859  if (isset($this->extNodes['result']['resultscore']['textstring'])) {
860  $ltiOutcome->setValue($this->extNodes['result']['resultscore']['textstring']);
861  }
862  // no break
863  case self::EXT_WRITE:
864  case self::EXT_DELETE:
865  $ok = true;
866  break;
867  }
868  }
869  }
870  }
871  if ((!$ok) && $this->hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) {
872  $className = $this->getApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode());
873  $hook = new $className($this);
874  $response = $hook->doOutcomesService($action, $ltiOutcome, $userResult);
875  if ($response !== false) {
876  $ok = true;
877  if ($action === self::EXT_READ) {
878  $ltiOutcome->setValue($response);
879  }
880  }
881  }
882 
883  return $ok;
884  }
885 
886  // /**
887  // * Perform a Memberships extension service request.
888  // * The userResult table is updated with any user objects with lis_result_sourcedid values.
889  // * @param bool $withGroups True is group information is to be requested as well
890  // * @return mixed Array of UserResult objects or False if the request was not successful
891  // *@deprecated Use getMemberships() instead
892  // * @see ResourceLink::getMemberships()
893  // */
894  // public function doMembershipsService(bool $withGroups = false)
895  // {
896  // Util::logDebug('Method ceLTIc\LTI\ResourceLink::doMembershipsService() has been deprecated; please use ceLTIc\LTI\ResourceLink::getMemberships() instead.',
897  // true);
898  // return $this->getMemberships($withGroups);
899  // }
900 
907  public function doSettingService(int $action, string $value = null)
908  {
909  $response = false;
910  $this->extResponse = '';
911  switch ($action) {
912  case self::EXT_READ:
913  $do = 'basic-lti-loadsetting';
914  break;
915  case self::EXT_WRITE:
916  $do = 'basic-lti-savesetting';
917  break;
918  case self::EXT_DELETE:
919  $do = 'basic-lti-deletesetting';
920  break;
921  }
922  if (isset($do)) {
923  $url = $this->getSetting('ext_ims_lti_tool_setting_url');
924  $params = array();
925  $params['id'] = $this->getSetting('ext_ims_lti_tool_setting_id');
926  if (is_null($value)) {
927  $value = '';
928  }
929  $params['setting'] = $value;
930 
931  if ($this->doService($do, $url, $params, 'https://purl.imsglobal.org/spec/lti-ext/scope/setting')) {
932  switch ($action) {
933  case self::EXT_READ:
934  if (isset($this->extNodes['setting']['value'])) {
935  $response = $this->extNodes['setting']['value'];
936  if (is_array($response)) {
937  $response = '';
938  }
939  }
940  break;
941  case self::EXT_WRITE:
942  $this->setSetting('ext_ims_lti_tool_setting', $value);
943  $this->saveSettings();
944  $response = true;
945  break;
946  case self::EXT_DELETE:
947  $response = true;
948  break;
949  }
950  }
951  }
952 
953  return $response;
954  }
955 
961  public function hasToolSettingsService(): bool
962  {
963  $has = !empty($this->getSetting('custom_link_setting_url'));
964  if (!$has) {
965  $has = self::hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this);
966  }
967  return $has;
968  }
969 
976  public function getToolSettings(int $mode = Service\ToolSettings::MODE_CURRENT_LEVEL, bool $simple = true)
977  {
978  $ok = false;
979  $settings = array();
980  if (!empty($this->getSetting('custom_link_setting_url'))) {
981  $url = $this->getSetting('custom_link_setting_url');
982  $service = new Service\ToolSettings($this, $url, $simple);
983  $settings = $service->get($mode);
984  $this->lastServiceRequest = $service->getHttpMessage();
985  $ok = $settings !== false;
986  }
987  if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) {
988  $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode());
989  $hook = new $className($this);
990  $settings = $hook->getToolSettings($mode, $simple);
991  }
992 
993  return $settings;
994  }
995 
1001  public function setToolSettings(array $settings = array()): bool
1002  {
1003  $ok = false;
1004  if (!empty($this->getSetting('custom_link_setting_url'))) {
1005  $url = $this->getSetting('custom_link_setting_url');
1006  $service = new Service\ToolSettings($this, $url);
1007  $ok = $service->set($settings);
1008  $this->lastServiceRequest = $service->getHttpMessage();
1009  }
1010  if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) {
1011  $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode());
1012  $hook = new $className($this);
1013  $ok = $hook->setToolSettings($settings);
1014  }
1015 
1016  return $ok;
1017  }
1018 
1027  public function hasMembershipService(): bool
1028  {
1030  'Method ceLTIc\LTI\ResourceLink::hasMembershipService() has been deprecated; please use ceLTIc\LTI\ResourceLink::hasMembershipsService() instead.',
1031  true
1032  );
1033  return $this->hasMembershipsService();
1034  }
1035 
1044  public function getMembership()
1045  {
1047  'Method ceLTIc\LTI\ResourceLink::getMembership() has been deprecated; please use ceLTIc\LTI\ResourceLink::getMemberships() instead.',
1048  true
1049  );
1050  return $this->getMemberships();
1051  }
1052 
1058  public function getMemberships(bool $withGroups = false)
1059  {
1060  $ok = false;
1061  $userResults = array();
1062  $hasLtiLinkService = !empty($this->getSetting('custom_link_memberships_url'));
1063  $hasLtiContextService = !empty($this->getContextId()) &&
1064  (!empty($this->getContext()->getSetting('custom_context_memberships_url')) || !empty($this->getContext()->getSetting('custom_context_memberships_v2_url')));
1065  $hasGroupsService = !empty($this->getContextId()) && !empty($this->getContext()->getSetting('custom_context_groups_url'));
1066  $hasExtService = !empty($this->getSetting('ext_ims_lis_memberships_url'));
1067  $hasApiHook = $this->hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this);
1068  if (($hasLtiContextService && (!$withGroups || $hasGroupsService)) || (!$hasExtService && !$hasApiHook)) {
1069  if (!empty($this->getContextId()) && !empty($this->getContext()->getSetting('custom_context_memberships_v2_url'))) {
1070  $url = $this->getContext()->getSetting('custom_context_memberships_v2_url');
1072  } else {
1073  $url = $this->getContext()->getSetting('custom_context_memberships_url');
1075  }
1076  $service = new Service\Membership($this, $url, $format);
1077  if (!$withGroups) {
1078  $userResults = $service->get();
1079  } else {
1080  $userResults = $service->getWithGroups();
1081  }
1082  $this->lastServiceRequest = $service->getHttpMessage();
1083  $ok = $userResults !== false;
1084  } elseif ($hasLtiLinkService) {
1085  $id = $this->id;
1086  $this->id = null;
1087  $url = $this->getSetting('custom_link_memberships_url');
1089  $service = new Service\Membership($this, $url, $format);
1090  if (!$withGroups) {
1091  $userResults = $service->get();
1092  } else {
1093  $userResults = $service->getWithGroups();
1094  }
1095  $this->lastServiceRequest = $service->getHttpMessage();
1096  $this->id = $id;
1097  $ok = $userResults !== false;
1098  }
1099  if (!$ok && $hasExtService) {
1100  $this->extResponse = '';
1101  $url = $this->getSetting('ext_ims_lis_memberships_url');
1102  $params = array();
1103  $params['id'] = $this->getSetting('ext_ims_lis_memberships_id');
1104  if ($withGroups) {
1105  $ok = $this->doService(
1106  'basic-lis-readmembershipsforcontextwithgroups',
1107  $url,
1108  $params,
1109  'https://purl.imsglobal.org/spec/lti-ext/scope/memberships'
1110  );
1111  }
1112  if (!$ok) {
1113  $ok = $this->doService(
1114  'basic-lis-readmembershipsforcontext',
1115  $url,
1116  $params,
1117  'https://purl.imsglobal.org/spec/lti-ext/scope/memberships'
1118  );
1119  }
1120  if ($ok) {
1121  $this->groupSets = array();
1122  $this->groups = array();
1123  if (isset($this->extNodes['memberships'])) {
1124  $memberships = $this->extNodes['memberships'];
1125  } elseif (isset($this->extNodes['members'])) {
1126  $memberships = $this->extNodes['members'];
1127  } else {
1128  $ok = false;
1129  }
1130  }
1131  if ($ok) {
1132  if (!isset($memberships['member'])) {
1133  $members = array();
1134  } elseif (!isset($memberships['member'][0])) {
1135  $members = array();
1136  $members[0] = $memberships['member'];
1137  } else {
1138  $members = $memberships['member'];
1139  }
1140 
1141  for ($i = 0; $i < count($members); $i++) {
1142  $userresult = UserResult::fromResourceLink($this, $members[$i]['user_id']);
1143 
1144  // Set the user name
1145  $firstname = (isset($members[$i]['person_name_given'])) ? $members[$i]['person_name_given'] : '';
1146  $lastname = (isset($members[$i]['person_name_family'])) ? $members[$i]['person_name_family'] : '';
1147  $fullname = (isset($members[$i]['person_name_full'])) ? $members[$i]['person_name_full'] : '';
1148  $userresult->setNames($firstname, $lastname, $fullname);
1149 
1150  // Set the sourcedId
1151  if (isset($members[$i]['person_sourcedid'])) {
1152  $userresult->sourcedId = $members[$i]['person_sourcedid'];
1153  }
1154 
1155  // Set the user email
1156  $email = (isset($members[$i]['person_contact_email_primary'])) ? $members[$i]['person_contact_email_primary'] : '';
1157  $userresult->setEmail($email, $this->getPlatform()->defaultEmail);
1158 
1159  // Set the user roles
1160  if (isset($members[$i]['roles'])) {
1161  $userresult->roles = Tool::parseRoles($members[$i]['roles']);
1162  }
1163 
1164  // Set the user groups
1165  if (!isset($members[$i]['groups']['group'])) {
1166  $groups = array();
1167  } elseif (!isset($members[$i]['groups']['group'][0])) {
1168  $groups = array();
1169  $groups[0] = $members[$i]['groups']['group'];
1170  } else {
1171  $groups = $members[$i]['groups']['group'];
1172  }
1173  for ($j = 0; $j < count($groups); $j++) {
1174  $group = $groups[$j];
1175  if (isset($group['set'])) {
1176  $set_id = $group['set']['id'];
1177  if (!isset($this->groupSets[$set_id])) {
1178  $this->groupSets[$set_id] = array('title' => $group['set']['title'], 'groups' => array(),
1179  'num_members' => 0, 'num_staff' => 0, 'num_learners' => 0);
1180  }
1181  $this->groupSets[$set_id]['num_members']++;
1182  if ($userresult->isStaff()) {
1183  $this->groupSets[$set_id]['num_staff']++;
1184  }
1185  if ($userresult->isLearner()) {
1186  $this->groupSets[$set_id]['num_learners']++;
1187  }
1188  if (!in_array($group['id'], $this->groupSets[$set_id]['groups'])) {
1189  $this->groupSets[$set_id]['groups'][] = $group['id'];
1190  }
1191  $this->groups[$group['id']] = array('title' => $group['title'], 'set' => $set_id);
1192  } else {
1193  $this->groups[$group['id']] = array('title' => $group['title']);
1194  }
1195  $userresult->groups[] = $group['id'];
1196  }
1197  if (isset($members[$i]['lis_result_sourcedid'])) {
1198  $userresult->ltiResultSourcedId = $members[$i]['lis_result_sourcedid'];
1199  }
1200  $userResults[] = $userresult;
1201  }
1202  } else {
1203  $userResults = false;
1204  }
1205  $ok = $userResults !== false;
1206  }
1207  if (!$ok && $hasApiHook) {
1208  $className = $this->getApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode());
1209  $hook = new $className($this);
1210  $userResults = $hook->getMemberships($withGroups);
1211  $ok = $userResults !== false;
1212  }
1213  if ($ok) {
1214  $oldUsers = $this->getUserResultSourcedIDs(true, Tool::ID_SCOPE_RESOURCE);
1215  foreach ($userResults as $userresult) {
1216  // If a result sourcedid is provided save the user
1217  if (!empty($userresult->ltiResultSourcedId)) {
1218  $userresult->save();
1219  }
1220  // Remove old user (if it exists)
1221  unset($oldUsers[$userresult->getId(Tool::ID_SCOPE_RESOURCE)]);
1222  }
1223  // Delete any old users which were not in the latest list from the platform
1224  foreach ($oldUsers as $id => $userresult) {
1225  $userresult->delete();
1226  }
1227  }
1228 
1229  return $userResults;
1230  }
1231 
1240  public function getUserResultSourcedIDs(bool $localOnly = false, int $idScope = null): array
1241  {
1242  return $this->getDataConnector()->getUserResultSourcedIDsResourceLink($this, $localOnly, $idScope);
1243  }
1244 
1250  public function getShares(): array
1251  {
1252  return $this->getDataConnector()->getSharesResourceLink($this);
1253  }
1254 
1262  public function getLineItems(string $resourceId = null, string $tag = null, int $limit = null)
1263  {
1264  $lineItems = false;
1265  $this->extRequest = '';
1266  $this->extRequestHeaders = '';
1267  $this->extResponse = '';
1268  $this->extResponseHeaders = '';
1269  $this->lastServiceRequest = null;
1270  $lineItemService = $this->getLineItemService();
1271  if (!empty($lineItemService)) {
1272  $lineItems = $lineItemService->getAll($this->ltiResourceLinkId, $resourceId, $tag, $limit);
1273  $http = $lineItemService->getHttpMessage();
1274  $this->extResponse = $http->response;
1275  $this->extResponseHeaders = $http->responseHeaders;
1276  $this->extRequest = $http->request;
1277  $this->extRequestHeaders = $http->requestHeaders;
1278  $this->lastServiceRequest = $http;
1279  }
1280 
1281  return $lineItems;
1282  }
1283 
1289  public function createLineItem(LineItem $lineItem): bool
1290  {
1291  $ok = false;
1292  $lineItemService = $this->getLineItemService();
1293  if (!empty($lineItemService)) {
1294  $lineItem->ltiResourceLinkId = $this->ltiResourceLinkId;
1295  $ok = $lineItemService->createLineItem($lineItem);
1296  }
1297 
1298  return $ok;
1299  }
1300 
1306  public function getOutcomes(int $limit = null)
1307  {
1308  $outcomes = false;
1309  $this->extRequest = '';
1310  $this->extRequestHeaders = '';
1311  $this->extResponse = '';
1312  $this->extResponseHeaders = '';
1313  $this->lastServiceRequest = null;
1314  $url = $this->getSetting('custom_lineitem_url');
1315  if (!empty($url)) {
1316  $resultService = new Service\Result($this->getPlatform(), $url);
1317  $outcomes = $resultService->getAll($limit);
1318  $http = $resultService->getHttpMessage();
1319  $this->extResponse = $http->response;
1320  $this->extResponseHeaders = $http->responseHeaders;
1321  $this->extRequest = $http->request;
1322  $this->extRequestHeaders = $http->requestHeaders;
1323  $this->lastServiceRequest = $http;
1324  }
1325 
1326  return $outcomes;
1327  }
1328 
1336  public function doAssessmentControlAction(AssessmentControlAction $assessmentControlAction, User $user, int $attemptNumber)
1337  {
1338  $status = false;
1339  $this->extRequest = '';
1340  $this->extRequestHeaders = '';
1341  $this->extResponse = '';
1342  $this->extResponseHeaders = '';
1343  $this->lastServiceRequest = null;
1344  $url = $this->getSetting('custom_ap_acs_url');
1345  if (!empty($url)) {
1346  $assessmentControlService = new Service\AssessmentControl($this, $url);
1347  $status = $assessmentControlService->submitAction($assessmentControlAction, $user, $attemptNumber);
1348  $http = $assessmentControlService->getHttpMessage();
1349  $this->extResponse = $http->response;
1350  $this->extResponseHeaders = $http->responseHeaders;
1351  $this->extRequest = $http->request;
1352  $this->extRequestHeaders = $http->requestHeaders;
1353  $this->lastServiceRequest = $http;
1354  }
1355 
1356  return $status;
1357  }
1358 
1359  // /**
1360  // * Class constructor from consumer.
1361  // *
1362  // * @deprecated Use fromPlatform() instead
1363  // * @see ResourceLink::fromPlatform()
1364  // *
1365  // * @param ToolConsumer $consumer Consumer object
1366  // * @param string $ltiResourceLinkId Resource link ID value
1367  // * @param string $tempId Temporary Resource link ID value (optional, default is null)
1368  // *
1369  // * @return ResourceLink
1370  // */
1371  // public static function fromConsumer($consumer, $ltiResourceLinkId, $tempId = null)
1372  // {
1373  // Util::logDebug('Method ceLTIc\LTI\ResourceLink::fromConsumer() has been deprecated; please use ceLTIc\LTI\ResourceLink::fromPlatform() instead.',
1374  // true);
1375  // return self::fromPlatform($consumer, $ltiResourceLinkId, $tempId);
1376  // }
1377 
1385  public static function fromPlatform(Platform $platform, string $ltiResourceLinkId, string $tempId = null): ResourceLink
1386  {
1387  $resourceLink = new ResourceLink();
1388  $resourceLink->platform = $platform;
1389  $resourceLink->dataConnector = $platform->getDataConnector();
1390  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1391  if (!empty($ltiResourceLinkId)) {
1392  $resourceLink->load();
1393  if (is_null($resourceLink->id) && !empty($tempId)) {
1394  $resourceLink->ltiResourceLinkId = $tempId;
1395  $resourceLink->load();
1396  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1397  }
1398  }
1399 
1400  return $resourceLink;
1401  }
1402 
1410  public static function fromContext(Context $context, string $ltiResourceLinkId, string $tempId = null): ResourceLink
1411  {
1412  $resourceLink = new ResourceLink();
1413  $resourceLink->setContext($context);
1414  $resourceLink->dataConnector = $context->getDataConnector();
1415  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1416  if (!empty($ltiResourceLinkId)) {
1417  $resourceLink->load();
1418  if (is_null($resourceLink->id) && !empty($tempId)) {
1419  $resourceLink->ltiResourceLinkId = $tempId;
1420  $resourceLink->load();
1421  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1422  }
1423  $resourceLink->setContext($context); // Ensure context remains set
1424  }
1425 
1426  return $resourceLink;
1427  }
1428 
1435  public static function fromRecordId(int $id, DataConnector $dataConnector): ResourceLink
1436  {
1437  $resourceLink = new ResourceLink();
1438  $resourceLink->dataConnector = $dataConnector;
1439  $resourceLink->load($id);
1440 
1441  return $resourceLink;
1442  }
1443 
1444  ###
1445  ### PRIVATE METHODS
1446  ###
1447 
1453  private function load(int $id = null): bool
1454  {
1455  $this->initialize();
1456  $this->id = $id;
1457 
1458  return $this->getDataConnector()->loadResourceLink($this);
1459  }
1460 
1467  private function checkValueType(Outcome $ltiOutcome, array $supportedTypes = null): bool
1468  {
1469  if (empty($supportedTypes)) {
1470  $supportedTypes = explode(
1471  ',',
1472  str_replace(' ', '', strtolower($this->getSetting('ext_ims_lis_resultvalue_sourcedids', self::EXT_TYPE_DECIMAL)))
1473  );
1474  }
1475  $type = $ltiOutcome->type;
1476  $value = $ltiOutcome->getValue();
1477  // Check whether the type is supported or there is no value
1478  $ok = in_array($type, $supportedTypes) || empty($value);
1479  if (!$ok) {
1480  // Convert numeric values to decimal
1481  if ($type === self::EXT_TYPE_PERCENTAGE) {
1482  if (substr($value, -1) === '%') {
1483  $value = substr($value, 0, -1);
1484  }
1485  $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1486  if ($ok) {
1487  $ltiOutcome->setValue($value / 100);
1488  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1489  }
1490  } elseif ($type === self::EXT_TYPE_RATIO) {
1491  $parts = explode('/', $value, 2);
1492  $ok = (count($parts) === 2) && is_numeric($parts[0]) && is_numeric($parts[1]) && ($parts[0] >= 0) && ($parts[1] > 0);
1493  if ($ok) {
1494  $ltiOutcome->setValue($parts[0] / $parts[1]);
1495  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1496  }
1497  // Convert letter_af to letter_af_plus or text
1498  } elseif ($type === self::EXT_TYPE_LETTER_AF) {
1499  if (in_array(self::EXT_TYPE_LETTER_AF_PLUS, $supportedTypes)) {
1500  $ok = true;
1501  $ltiOutcome->type = self::EXT_TYPE_LETTER_AF_PLUS;
1502  } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1503  $ok = true;
1504  $ltiOutcome->type = self::EXT_TYPE_TEXT;
1505  }
1506  // Convert letter_af_plus to letter_af or text
1507  } elseif ($type === self::EXT_TYPE_LETTER_AF_PLUS) {
1508  if (in_array(self::EXT_TYPE_LETTER_AF, $supportedTypes) && (strlen($value) === 1)) {
1509  $ok = true;
1510  $ltiOutcome->type = self::EXT_TYPE_LETTER_AF;
1511  } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1512  $ok = true;
1513  $ltiOutcome->type = self::EXT_TYPE_TEXT;
1514  }
1515  // Convert text to decimal
1516  } elseif ($type === self::EXT_TYPE_TEXT) {
1517  $ok = is_numeric($value) && ($value >= 0) && ($value <= 1);
1518  if ($ok) {
1519  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1520  } elseif (substr($value, -1) === '%') {
1521  $value = substr($value, 0, -1);
1522  $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1523  if ($ok) {
1524  if (in_array(self::EXT_TYPE_PERCENTAGE, $supportedTypes)) {
1525  $ltiOutcome->type = self::EXT_TYPE_PERCENTAGE;
1526  } else {
1527  $ltiOutcome->setValue($value / 100);
1528  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1529  }
1530  }
1531  }
1532  }
1533  }
1534 
1535  return $ok;
1536  }
1537 
1546  private function doService(string $type, string $url, array $params, string $scope): bool
1547  {
1548  $ok = false;
1549  $this->extRequest = '';
1550  $this->extRequestHeaders = '';
1551  $this->extResponse = '';
1552  $this->extResponseHeaders = '';
1553  $this->lastServiceRequest = null;
1554  if (!empty($url)) {
1555  $params['lti_version'] = $this->getPlatform()->ltiVersion;
1556  $params['lti_message_type'] = $type;
1557  $retry = false;
1558  $newToken = false;
1559  if (!$this->getPlatform()->useOAuth1()) {
1560  $accessToken = $this->platform->getAccessToken();
1561  $retry = true;
1562  if (empty($accessToken)) {
1563  $accessToken = new AccessToken($this->platform);
1564  $this->platform->setAccessToken($accessToken);
1565  }
1566  if (!$accessToken->hasScope($scope) && (empty(Tool::$defaultTool) || !in_array(
1567  $scope,
1568  Tool::$defaultTool->requiredScopes
1569  ))) {
1570  $accessToken->expires = time();
1571  $accessToken->get($scope, true);
1572  $this->platform->setAccessToken($accessToken);
1573  $newToken = true;
1574  }
1575  }
1576  do {
1577  // Add message signature
1578  $signed = $this->getPlatform()->addSignature($url, $params, 'POST', 'application/x-www-form-urlencoded');
1579  // Connect to platform
1580  if (is_array($signed)) {
1581  $http = new HttpMessage($url, 'POST', $signed);
1582  } else {
1583  $http = new HttpMessage($url, 'POST', $params, $signed);
1584  }
1585  if ($http->send()) {
1586  // Parse XML response
1587  $this->extResponse = $http->response;
1588  $this->extResponseHeaders = $http->responseHeaders;
1589  try {
1590  $this->extDoc = new DOMDocument();
1591  $this->extDoc->loadXML($http->response);
1592  $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement);
1593  if (isset($this->extNodes['statusinfo']['codemajor']) && ($this->extNodes['statusinfo']['codemajor'] === 'Success')) {
1594  $ok = true;
1595  }
1596  } catch (\Exception $e) {
1597  }
1598  }
1599  $retry = $retry && !$newToken && !$ok;
1600  if ($retry) { // Obtain a new access token just for the required scope
1601  $accessToken = $this->platform->getAccessToken();
1602  $accessToken->expires = time();
1603  $accessToken->get($scope, true);
1604  $this->platform->setAccessToken($accessToken);
1605  $newToken = true;
1606  }
1607  } while ($retry);
1608  $this->extRequest = $http->request;
1609  $this->extRequestHeaders = $http->requestHeaders;
1610  $this->lastServiceRequest = $http;
1611  }
1612 
1613  return $ok;
1614  }
1615 
1622  private function doResultService(UserResult $userResult, string $url): ?Outcome
1623  {
1624  $outcome = null;
1625  $this->extRequest = '';
1626  $this->extRequestHeaders = '';
1627  $this->extResponse = '';
1628  $this->extResponseHeaders = '';
1629  $this->lastServiceRequest = null;
1630  if (!empty($url)) {
1631  $resultService = new Service\Result($this->getPlatform(), $url);
1632  $outcome = $resultService->get($userResult);
1633  $http = $resultService->getHttpMessage();
1634  $this->extResponse = $http->response;
1635  $this->extResponseHeaders = $http->responseHeaders;
1636  $this->extRequest = $http->request;
1637  $this->extRequestHeaders = $http->requestHeaders;
1638  $this->lastServiceRequest = $http;
1639  }
1640 
1641  return $outcome;
1642  }
1643 
1651  private function doScoreService(Outcome $ltiOutcome, UserResult $userResult, string $url): bool
1652  {
1653  $ok = false;
1654  $this->extRequest = '';
1655  $this->extRequestHeaders = '';
1656  $this->extResponse = '';
1657  $this->extResponseHeaders = '';
1658  $this->lastServiceRequest = null;
1659  if (!empty($url)) {
1660  $scoreService = new Service\Score($this->getPlatform(), $url);
1661  $scoreService->submit($ltiOutcome, $userResult);
1662  $http = $scoreService->getHttpMessage();
1663  $this->extResponse = $http->response;
1664  $this->extResponseHeaders = $http->responseHeaders;
1665  $ok = $http->ok;
1666  $this->extRequest = $http->request;
1667  $this->extRequestHeaders = $http->requestHeaders;
1668  $this->lastServiceRequest = $http;
1669  }
1670 
1671  return $ok;
1672  }
1673 
1681  private function doLTI11Service(string $type, string $url, string $xml): bool
1682  {
1683  $ok = false;
1684  $this->extRequest = '';
1685  $this->extRequestHeaders = '';
1686  $this->extResponse = '';
1687  $this->extResponseHeaders = '';
1688  $this->lastServiceRequest = null;
1689  if (!empty($url)) {
1690  $id = uniqid();
1691  $xmlRequest = <<< EOD
1692 <?xml version = "1.0" encoding = "UTF-8"?>
1693 <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
1694  <imsx_POXHeader>
1695  <imsx_POXRequestHeaderInfo>
1696  <imsx_version>V1.0</imsx_version>
1697  <imsx_messageIdentifier>{$id}</imsx_messageIdentifier>
1698  </imsx_POXRequestHeaderInfo>
1699  </imsx_POXHeader>
1700  <imsx_POXBody>
1701  <{$type}Request>
1702 {$xml}
1703  </{$type}Request>
1704  </imsx_POXBody>
1705 </imsx_POXEnvelopeRequest>
1706 EOD;
1707  $scope = 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome';
1708  $retry = false;
1709  $newToken = false;
1710  if (!$this->getPlatform()->useOAuth1()) {
1711  $accessToken = $this->platform->getAccessToken();
1712  $retry = true;
1713  if (empty($accessToken)) {
1714  $accessToken = new AccessToken($this->platform);
1715  $this->platform->setAccessToken($accessToken);
1716  }
1717  if (!$accessToken->hasScope($scope) && (empty(Tool::$defaultTool) || !in_array(
1718  $scope,
1719  Tool::$defaultTool->requiredScopes
1720  ))) {
1721  $accessToken->expires = time();
1722  $accessToken->get($scope, true);
1723  $this->platform->setAccessToken($accessToken);
1724  $newToken = true;
1725  }
1726  }
1727  do {
1728  // Add message signature
1729  $header = $this->getPlatform()->addSignature($url, $xmlRequest, 'POST', 'application/xml');
1730  // Connect to platform
1731  $http = new \ILIAS\LTI\ToolProvider\Http\HttpMessage($url, 'POST', $xmlRequest, $header);
1732  if ($http->send()) {
1733  // Parse XML response
1734  $this->extResponse = $http->response;
1735  $this->extResponseHeaders = $http->responseHeaders;
1736  try {
1737  $this->extDoc = new DOMDocument();
1738  $this->extDoc->loadXML($http->response);
1739  $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement);
1740  if (isset($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor']) &&
1741  ($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor'] === 'success')) {
1742  $ok = true;
1743  }
1744  } catch (\Exception $e) {
1745  }
1746  }
1747  $retry = $retry && !$newToken && !$ok;
1748  if ($retry) { // Obtain a new access token just for the required scope
1749  $accessToken = $this->platform->getAccessToken();
1750  $accessToken->expires = time();
1751  $accessToken->get($scope, true);
1752  $this->platform->setAccessToken($accessToken);
1753  $newToken = true;
1754  }
1755  } while ($retry);
1756  $this->extRequest = $http->request;
1757  $this->extRequestHeaders = $http->requestHeaders;
1758  $this->lastServiceRequest = $http;
1759  }
1760 
1761  return $ok;
1762  }
1763 
1769  private function getLineItemService()
1770  {
1771  $url = $this->getSetting('custom_lineitems_url');
1772  if (!empty($url)) {
1773  $lineItemService = new Service\LineItem($this->getPlatform(), $url);
1774  } else {
1775  $lineItemService = false;
1776  }
1777 
1778  return $lineItemService;
1779  }
1780 
1786  private function domnodeToArray($node)
1787  {
1788  $output = array();
1789  switch ($node->nodeType) {
1790  case XML_CDATA_SECTION_NODE:
1791  case XML_TEXT_NODE:
1792  $output = trim($node->textContent);
1793  break;
1794  case XML_ELEMENT_NODE:
1795  for ($i = 0; $i < $node->childNodes->length; $i++) {
1796  $child = $node->childNodes->item($i);
1797  $v = $this->domnodeToArray($child);
1798  if (isset($child->tagName)) {
1799  $output[$child->tagName][] = $v;
1800  } else {
1801  $s = (string) $v;
1802  if (!empty($s)) {
1803  $output = $s;
1804  }
1805  }
1806  }
1807  if (is_array($output)) {
1808  if ($node->hasAttributes()) {
1809  foreach ($node->attributes as $attrNode) {
1810  $output['@attributes'][$attrNode->name] = (string) $attrNode->value;
1811  }
1812  }
1813  foreach ($output as $t => $v) {
1814  if (is_array($v) && (count($v) === 1) && ($t !== '@attributes')) {
1815  $output[$t] = $v[0];
1816  }
1817  }
1818  }
1819  break;
1820  }
1821 
1822  return $output;
1823  }
1824 }
getRecordId()
Get the context record ID.
Definition: Context.php:283
getResourceLink()
Get resource link.
Definition: UserResult.php:133
getDataConnector()
Get the data connector.
Definition: Platform.php:328
static string $MEMBERSHIPS_SERVICE_HOOK
Memberships service hook name.
Definition: ApiHook.php:48
const MODE_CURRENT_LEVEL
Settings at current level mode.
Class to represent a platform.
Definition: Platform.php:35
Class to represent an assessment control action.
$scope
Definition: ltiregstart.php:53
static hasConfiguredApiHook(string $hookName, string $familyCode, $sourceObject)
Check if an API hook is registered and configured.
Definition: ApiHook.php:115
Class to represent an HTTP message.
Definition: AccessToken.php:32
const MEDIA_TYPE_MEMBERSHIPS_NRPS
Media type for Names and Role Provisioning service.
Definition: Membership.php:42
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
static logDebug(string $message, bool $showSource=false)
Log a debug message.
Definition: Util.php:363
Class to implement a service.
Definition: Service.php:33
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
Definition: ltiregstart.php:33
Class to represent a platform user.
Definition: User.php:30
$scopes
Definition: ltitoken.php:99
const MEDIA_TYPE_MEMBERSHIPS_V1
Media type for version 1 of Memberships service.
Definition: Membership.php:37
static fromRecordId(int $id, \ILIAS\LTI\ToolProvider\DataConnector\DataConnector $dataConnector)
Load the context from the database.
Definition: Context.php:664
Class to represent a line item.
Definition: LineItem.php:30
Class to implement the Membership service.
Definition: Membership.php:32
$response
Definition: xapitoken.php:93
Class to implement the Result service.
Definition: Result.php:31
static getApiHook(string $hookName, string $familyCode)
Get the class name for an API hook.
Definition: ApiHook.php:88
Class to represent a platform user.
Definition: UserResult.php:30
static Tool $defaultTool
Default tool for use with service requests.
Definition: Tool.php:299
static string $OUTCOMES_SERVICE_HOOK
Outcomes service hook name.
Definition: ApiHook.php:53
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: AccessToken.php:19
Class to implement the Tool Settings service.
static fromRecordId(int $id, ilLTIDataConnector $dataConnector)
Load the platform from the database by its record ID.
setValue(string $value)
Set the outcome value.
Definition: Outcome.php:167
$url
Definition: ltiregstart.php:35
$http
Definition: raiseError.php:7
static string $SCOPE
Access scope.
Definition: Result.php:36
$comment
Definition: buildRTE.php:72
trait ApiHook
Trait to handle API hook registrations.
Definition: ApiHook.php:29
static parseRoles($roles, string $ltiVersion=Util::LTI_VERSION1)
Get an array of fully qualified user roles.
Definition: System.php:538
useOAuth1()
Determine whether this consumer is using the OAuth 1 security model.
Definition: System.php:698
Class to provide a connection to a persistent store for LTI objects.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getPointsPossible()
Get the points possible value.
Definition: Outcome.php:177
Class to implement the Assessment Control service.
Class to represent an outcome.
Definition: Outcome.php:28
Class to implement the Score service.
Definition: Score.php:30
Class to represent a platform context.
Definition: Context.php:33
$service
Definition: ltiservices.php:43
getDataConnector()
Get the data connector.
Definition: Context.php:302
getValue()
Get the outcome value.
Definition: Outcome.php:158
static string $SCOPE
Access scope.
Definition: Score.php:35
static string $TOOL_SETTINGS_SERVICE_HOOK
Tool Settings service hook name.
Definition: ApiHook.php:58
const ID_SCOPE_RESOURCE
Prefix the ID with the consumer key and resource ID.
Definition: Tool.php:66
static fromResourceLink(ResourceLink $resourceLink, string $ltiUserId)
Class constructor from resource link.
Definition: UserResult.php:265