ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
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  $this->platform = Platform::fromRecordId($this->platformId, $this->getDataConnector());
388  }
389  }
390 
391  return $this->platform;
392  }
393 
399  public function getPlatformId(): ?int
400  {
401  return $this->platformId;
402  }
403 
408  public function setPlatformId(?int $platformId)
409  {
410  $this->platform = null;
411  $this->platformId = $platformId;
412  }
413 
419  public function getContext(): ?Context
420  {
421  if (is_null($this->context) && !is_null($this->contextId)) {
422  $this->context = Context::fromRecordId($this->contextId, $this->getDataConnector());
423  }
424 
425  return $this->context;
426  }
427 
433  public function getContextId(): ?int
434  {
435  if (is_null($this->contextId) && !is_null($this->context)) {
436  $this->contextId = $this->context->getRecordId();
437  }
438 
439  return $this->contextId;
440  }
441 
446  public function setContext(Context $context)
447  {
448  $this->context = $context;
449  $this->contextId = $context->getRecordId();
450  }
451 
456  public function setContextId(?int $contextId)
457  {
458  if ($this->contextId !== $contextId) {
459  $this->context = null;
460  $this->contextId = $contextId;
461  }
462  }
463 
469  public function getKey(): string
470  {
471  return $this->getPlatform()->getKey();
472  }
473 
479  public function getId(): ?string
480  {
482  }
483 
489  public function getRecordId(): ?int
490  {
491  return $this->id;
492  }
493 
498  public function setRecordId(int $id)
499  {
500  $this->id = $id;
501  }
502 
508  public function getDataConnector(): ?DataConnector
509  {
510  if (empty($this->dataConnector)) {
511  $this->getPlatform();
512  if (!empty($this->platform)) {
513  $this->dataConnector = $this->platform->getDataConnector();
514  }
515  }
516 
517  return $this->dataConnector;
518  }
519 
526  public function getSetting(string $name, string $default = ''): string
527  {
528  if (array_key_exists($name, $this->settings)) {
529  $value = $this->settings[$name];
530  } else {
531  $value = $default;
532  }
533 
534  return $value;
535  }
536 
542  public function setSetting(string $name, string $value = null)
543  {
544  $old_value = $this->getSetting($name);
545  if ($value !== $old_value) {
546  if (!empty($value)) {
547  $this->settings[$name] = $value;
548  } else {
549  unset($this->settings[$name]);
550  }
551  $this->settingsChanged = true;
552  }
553  }
554 
560  public function getSettings(): ?array
561  {
562  return $this->settings;
563  }
564 
569  public function setSettings(array $settings)
570  {
571  $this->settings = $settings;
572  }
573 
579  public function saveSettings(): bool
580  {
581  if ($this->settingsChanged) {
582  $ok = $this->save();
583  } else {
584  $ok = true;
585  }
586 
587  return $ok;
588  }
589 
595  public function hasOutcomesService(): bool
596  {
597  $has = !empty($this->getSetting('ext_ims_lis_basic_outcome_url')) || !empty($this->getSetting('lis_outcome_service_url'));
598  if (!$has && !empty($this->getSetting('custom_lineitem_url')) && !empty($this->getSetting('custom_ags_scopes'))) {
599  $scopes = explode(',', $this->getSetting('custom_ags_scopes'));
600  $has = in_array(Service\Score::$SCOPE, $scopes) && in_array(Service\Result::$SCOPE, $scopes);
601  }
602  if (!$has) {
603  $has = self::hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this);
604  }
605  return $has;
606  }
607 
613  public function hasMembershipsService(): bool
614  {
615  $has = false;
616  if (!empty($this->getContextId())) {
617  $has = !empty($this->getContext()->getSetting('custom_context_memberships_url')) || !empty($this->getContext()->getSetting('custom_context_memberships_v2_url'));
618  }
619  if (!$has) {
620  $has = !empty($this->getSetting('custom_link_memberships_url'));
621  }
622  if (!$has) {
623  $has = !empty($this->getSetting('ext_ims_lis_memberships_url'));
624  }
625  if (!$has) {
626  $has = self::hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this);
627  }
628 
629  return $has;
630  }
631 
637  public function hasSettingService(): bool
638  {
639  $url = $this->getSetting('ext_ims_lti_tool_setting_url');
640 
641  return !empty($url);
642  }
643 
649  public function hasLineItemService(): bool
650  {
651  $has = false;
652  if (!empty($this->getSetting('custom_ags_scopes'))) {
653  $scopes = explode(',', $this->getSetting('custom_ags_scopes'));
654  if (in_array(Service\LineItem::$SCOPE, $scopes) || in_array(Service\LineItem::$SCOPE_READONLY, $scopes)) {
655  $has = !empty($this->getSetting('custom_lineitems_url'));
656  }
657  }
658 
659  return $has;
660  }
661 
667  public function hasScoreService(): bool
668  {
669  $has = false;
670  if (!empty($this->getSetting('custom_ags_scopes'))) {
671  $scopes = explode(',', $this->getSetting('custom_ags_scopes'));
672  if (in_array(Service\Score::$SCOPE, $scopes)) {
673  $has = !empty($this->getSetting('custom_lineitem_url'));
674  }
675  }
676 
677  return $has;
678  }
679 
685  public function hasResultService(): bool
686  {
687  $has = false;
688  if (!empty($this->getSetting('custom_ags_scopes'))) {
689  $scopes = explode(',', $this->getSetting('custom_ags_scopes'));
690  if (in_array(Service\Result::$SCOPE, $scopes)) {
691  $has = !empty($this->getSetting('custom_lineitem_url'));
692  }
693  }
694 
695  return $has;
696  }
697 
703  public function hasAssessmentControlService(): bool
704  {
705  $url = $this->getSetting('custom_ap_acs_url');
706 
707  return !empty($url);
708  }
709 
717  public function doOutcomesService(int $action, Outcome $ltiOutcome, UserResult $userResult): bool
718  {
719  $ok = false;
720  $this->extResponse = '';
721  // Lookup service details from the source resource link appropriate to the user (in case the destination is being shared)
722  $sourceResourceLink = $userResult->getResourceLink();
723  $sourcedId = $userResult->ltiResultSourcedId;
724 
725  // Use LTI 1.1 service in preference to extension service if it is available
726  $urlAGS = $sourceResourceLink->getSetting('custom_lineitem_url');
727  $urlLTI11 = $sourceResourceLink->getSetting('lis_outcome_service_url');
728  $urlExt = $sourceResourceLink->getSetting('ext_ims_lis_basic_outcome_url');
729 
730  if (!empty($urlAGS)) {
731  if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL) && $sourceResourceLink->hasResultService()) {
732  $ltiOutcome = $this->doResultService($userResult, $urlAGS);
733  $ok = !empty($ltiOutcome);
734  } elseif ((($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL)) && $sourceResourceLink->hasScoreService()) ||
735  ($action === self::EXT_DELETE)) {
736  if ($action === self::EXT_DELETE) {
737  $ltiOutcome->setValue(null);
738  $ltiOutcome->activityProgress = 'Initialized';
739  $ltiOutcome->gradingProgress = 'NotReady';
740  }
741  $ok = $this->doScoreService($ltiOutcome, $userResult, $urlAGS);
742  }
743  }
744  if (!$ok && is_null($ltiOutcome->getValue())) {
745  $ltiOutcome->setValue('');
746  }
747  if (!$ok && !empty($urlLTI11)) {
748  $do = '';
749  $outcome = $ltiOutcome->getValue();
750  if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
751  $do = 'readResult';
752  } elseif (($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
753  $do = 'replaceResult';
754  if (($ltiOutcome->getPointsPossible() <> 1) && ($ltiOutcome->getPointsPossible() > 0)) {
755  $outcome = $outcome / $ltiOutcome->getPointsPossible();
756  }
757  } elseif ($action === self::EXT_DELETE) {
758  $do = 'deleteResult';
759  }
760  if (!empty($do)) {
761  $xml = '';
762  if ($action === self::EXT_WRITE) {
763  $comment = (empty($ltiOutcome->comment)) ? '' : trim($ltiOutcome->comment);
764  if (!empty($comment) && !empty($sourceResourceLink->getSetting('ext_outcome_data_values_accepted'))) {
765  $resultDataTypes = explode(',', $sourceResourceLink->getSetting('ext_outcome_data_values_accepted'));
766  $resultDataType = '';
767  if (count($resultDataTypes) === 1) {
768  $resultDataType = $resultDataTypes[0];
769  } elseif (count($resultDataTypes) > 1) {
770  $isUrl = (strpos($comment, 'http://') === 0) || (strpos($comment, 'https://') === 0);
771  if ($isUrl && in_array('ltiLaunchUrl', $resultDataTypes)) {
772  $resultDataType = 'ltiLaunchUrl';
773  } elseif ($isUrl && in_array('url', $resultDataTypes)) {
774  $resultDataType = 'url';
775  } elseif (in_array('text', $resultDataTypes)) {
776  $resultDataType = 'text';
777  }
778  }
779  if (!empty($resultDataType)) {
780  $xml = <<< EOF
781 
782  <resultData>
783  <{$resultDataType}>{$comment}</{$resultDataType}>
784  </resultData>
785 EOF;
786  }
787  }
788  $xml = <<< EOF
789 
790  <result>
791  <resultScore>
792  <language>{$ltiOutcome->language}</language>
793  <textString>{$outcome}</textString>
794  </resultScore>{$xml}
795  </result>
796 EOF;
797  }
798  $sourcedId = htmlentities($sourcedId);
799  $xml = <<<EOF
800  <resultRecord>
801  <sourcedGUID>
802  <sourcedId>{$sourcedId}</sourcedId>
803  </sourcedGUID>{$xml}
804  </resultRecord>
805 EOF;
806  if ($this->doLTI11Service($do, $urlLTI11, $xml)) {
807  switch ($action) {
808  case self::EXT_READ:
809  if (!isset($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString'])) {
810  break;
811  } else {
812  $ltiOutcome->setValue($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString']);
813  }
814  // no break
815  case self::EXT_WRITE:
816  case self::EXT_DELETE:
817  $ok = true;
818  break;
819  }
820  }
821  }
822  }
823  if (!$ok && !empty($urlExt)) {
824  $do = '';
825  $outcome = $ltiOutcome->getValue();
826  if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
827  $do = 'basic-lis-readresult';
828  } elseif (($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
829  $do = 'basic-lis-updateresult';
830  if (($ltiOutcome->getPointsPossible() <> 1) && ($ltiOutcome->getPointsPossible() > 0)) {
831  $outcome = $outcome / $ltiOutcome->getPointsPossible();
832  }
833  } elseif ($action === self::EXT_DELETE) {
834  $do = 'basic-lis-deleteresult';
835  }
836  if (!empty($do)) {
837  $params = array();
838  $params['sourcedid'] = $sourcedId;
839  $params['result_resultscore_textstring'] = $outcome;
840  if (!empty($ltiOutcome->language)) {
841  $params['result_resultscore_language'] = $ltiOutcome->language;
842  }
843  if (!empty($ltiOutcome->status)) {
844  $params['result_statusofresult'] = $ltiOutcome->status;
845  }
846  if (!empty($ltiOutcome->date)) {
847  $params['result_date'] = $ltiOutcome->date;
848  }
849  if (!empty($ltiOutcome->type)) {
850  $params['result_resultvaluesourcedid'] = $ltiOutcome->type;
851  }
852  if (!empty($ltiOutcome->dataSource)) {
853  $params['result_datasource'] = $ltiOutcome->dataSource;
854  }
855  if ($this->doService($do, $urlExt, $params, 'https://purl.imsglobal.org/spec/lti-ext/scope/outcomes')) {
856  switch ($action) {
857  case self::EXT_READ:
858  if (isset($this->extNodes['result']['resultscore']['textstring'])) {
859  $ltiOutcome->setValue($this->extNodes['result']['resultscore']['textstring']);
860  }
861  // no break
862  case self::EXT_WRITE:
863  case self::EXT_DELETE:
864  $ok = true;
865  break;
866  }
867  }
868  }
869  }
870  if ((!$ok) && $this->hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) {
871  $className = $this->getApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getPlatform()->getFamilyCode());
872  $hook = new $className($this);
873  $response = $hook->doOutcomesService($action, $ltiOutcome, $userResult);
874  if ($response !== false) {
875  $ok = true;
876  if ($action === self::EXT_READ) {
877  $ltiOutcome->setValue($response);
878  }
879  }
880  }
881 
882  return $ok;
883  }
884 
885 // /**
886 // * Perform a Memberships extension service request.
887 // * The userResult table is updated with any user objects with lis_result_sourcedid values.
888 // * @param bool $withGroups True is group information is to be requested as well
889 // * @return mixed Array of UserResult objects or False if the request was not successful
890 // *@deprecated Use getMemberships() instead
891 // * @see ResourceLink::getMemberships()
892 // */
893 // public function doMembershipsService(bool $withGroups = false)
894 // {
895 // Util::logDebug('Method ceLTIc\LTI\ResourceLink::doMembershipsService() has been deprecated; please use ceLTIc\LTI\ResourceLink::getMemberships() instead.',
896 // true);
897 // return $this->getMemberships($withGroups);
898 // }
899 
906  public function doSettingService(int $action, string $value = null)
907  {
908  $response = false;
909  $this->extResponse = '';
910  switch ($action) {
911  case self::EXT_READ:
912  $do = 'basic-lti-loadsetting';
913  break;
914  case self::EXT_WRITE:
915  $do = 'basic-lti-savesetting';
916  break;
917  case self::EXT_DELETE:
918  $do = 'basic-lti-deletesetting';
919  break;
920  }
921  if (isset($do)) {
922  $url = $this->getSetting('ext_ims_lti_tool_setting_url');
923  $params = array();
924  $params['id'] = $this->getSetting('ext_ims_lti_tool_setting_id');
925  if (is_null($value)) {
926  $value = '';
927  }
928  $params['setting'] = $value;
929 
930  if ($this->doService($do, $url, $params, 'https://purl.imsglobal.org/spec/lti-ext/scope/setting')) {
931  switch ($action) {
932  case self::EXT_READ:
933  if (isset($this->extNodes['setting']['value'])) {
934  $response = $this->extNodes['setting']['value'];
935  if (is_array($response)) {
936  $response = '';
937  }
938  }
939  break;
940  case self::EXT_WRITE:
941  $this->setSetting('ext_ims_lti_tool_setting', $value);
942  $this->saveSettings();
943  $response = true;
944  break;
945  case self::EXT_DELETE:
946  $response = true;
947  break;
948  }
949  }
950  }
951 
952  return $response;
953  }
954 
960  public function hasToolSettingsService(): bool
961  {
962  $has = !empty($this->getSetting('custom_link_setting_url'));
963  if (!$has) {
964  $has = self::hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this);
965  }
966  return $has;
967  }
968 
975  public function getToolSettings(int $mode = Service\ToolSettings::MODE_CURRENT_LEVEL, bool $simple = true)
976  {
977  $ok = false;
978  $settings = array();
979  if (!empty($this->getSetting('custom_link_setting_url'))) {
980  $url = $this->getSetting('custom_link_setting_url');
981  $service = new Service\ToolSettings($this, $url, $simple);
982  $settings = $service->get($mode);
983  $this->lastServiceRequest = $service->getHttpMessage();
984  $ok = $settings !== false;
985  }
986  if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) {
987  $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode());
988  $hook = new $className($this);
989  $settings = $hook->getToolSettings($mode, $simple);
990  }
991 
992  return $settings;
993  }
994 
1000  public function setToolSettings(array $settings = array()): bool
1001  {
1002  $ok = false;
1003  if (!empty($this->getSetting('custom_link_setting_url'))) {
1004  $url = $this->getSetting('custom_link_setting_url');
1005  $service = new Service\ToolSettings($this, $url);
1006  $ok = $service->set($settings);
1007  $this->lastServiceRequest = $service->getHttpMessage();
1008  }
1009  if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this)) {
1010  $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode());
1011  $hook = new $className($this);
1012  $ok = $hook->setToolSettings($settings);
1013  }
1014 
1015  return $ok;
1016  }
1017 
1026  public function hasMembershipService(): bool
1027  {
1029  'Method ceLTIc\LTI\ResourceLink::hasMembershipService() has been deprecated; please use ceLTIc\LTI\ResourceLink::hasMembershipsService() instead.',
1030  true
1031  );
1032  return $this->hasMembershipsService();
1033  }
1034 
1043  public function getMembership()
1044  {
1046  'Method ceLTIc\LTI\ResourceLink::getMembership() has been deprecated; please use ceLTIc\LTI\ResourceLink::getMemberships() instead.',
1047  true
1048  );
1049  return $this->getMemberships();
1050  }
1051 
1057  public function getMemberships(bool $withGroups = false)
1058  {
1059  $ok = false;
1060  $userResults = array();
1061  $hasLtiLinkService = !empty($this->getSetting('custom_link_memberships_url'));
1062  $hasLtiContextService = !empty($this->getContextId()) &&
1063  (!empty($this->getContext()->getSetting('custom_context_memberships_url')) || !empty($this->getContext()->getSetting('custom_context_memberships_v2_url')));
1064  $hasGroupsService = !empty($this->getContextId()) && !empty($this->getContext()->getSetting('custom_context_groups_url'));
1065  $hasExtService = !empty($this->getSetting('ext_ims_lis_memberships_url'));
1066  $hasApiHook = $this->hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode(), $this);
1067  if (($hasLtiContextService && (!$withGroups || $hasGroupsService)) || (!$hasExtService && !$hasApiHook)) {
1068  if (!empty($this->getContextId()) && !empty($this->getContext()->getSetting('custom_context_memberships_v2_url'))) {
1069  $url = $this->getContext()->getSetting('custom_context_memberships_v2_url');
1071  } else {
1072  $url = $this->getContext()->getSetting('custom_context_memberships_url');
1074  }
1075  $service = new Service\Membership($this, $url, $format);
1076  if (!$withGroups) {
1077  $userResults = $service->get();
1078  } else {
1079  $userResults = $service->getWithGroups();
1080  }
1081  $this->lastServiceRequest = $service->getHttpMessage();
1082  $ok = $userResults !== false;
1083  } elseif ($hasLtiLinkService) {
1084  $id = $this->id;
1085  $this->id = null;
1086  $url = $this->getSetting('custom_link_memberships_url');
1088  $service = new Service\Membership($this, $url, $format);
1089  if (!$withGroups) {
1090  $userResults = $service->get();
1091  } else {
1092  $userResults = $service->getWithGroups();
1093  }
1094  $this->lastServiceRequest = $service->getHttpMessage();
1095  $this->id = $id;
1096  $ok = $userResults !== false;
1097  }
1098  if (!$ok && $hasExtService) {
1099  $this->extResponse = '';
1100  $url = $this->getSetting('ext_ims_lis_memberships_url');
1101  $params = array();
1102  $params['id'] = $this->getSetting('ext_ims_lis_memberships_id');
1103  if ($withGroups) {
1104  $ok = $this->doService(
1105  'basic-lis-readmembershipsforcontextwithgroups',
1106  $url,
1107  $params,
1108  'https://purl.imsglobal.org/spec/lti-ext/scope/memberships'
1109  );
1110  }
1111  if (!$ok) {
1112  $ok = $this->doService(
1113  'basic-lis-readmembershipsforcontext',
1114  $url,
1115  $params,
1116  'https://purl.imsglobal.org/spec/lti-ext/scope/memberships'
1117  );
1118  }
1119  if ($ok) {
1120  $this->groupSets = array();
1121  $this->groups = array();
1122  if (isset($this->extNodes['memberships'])) {
1123  $memberships = $this->extNodes['memberships'];
1124  } elseif (isset($this->extNodes['members'])) {
1125  $memberships = $this->extNodes['members'];
1126  } else {
1127  $ok = false;
1128  }
1129  }
1130  if ($ok) {
1131  if (!isset($memberships['member'])) {
1132  $members = array();
1133  } elseif (!isset($memberships['member'][0])) {
1134  $members = array();
1135  $members[0] = $memberships['member'];
1136  } else {
1137  $members = $memberships['member'];
1138  }
1139 
1140  for ($i = 0; $i < count($members); $i++) {
1141  $userresult = UserResult::fromResourceLink($this, $members[$i]['user_id']);
1142 
1143  // Set the user name
1144  $firstname = (isset($members[$i]['person_name_given'])) ? $members[$i]['person_name_given'] : '';
1145  $lastname = (isset($members[$i]['person_name_family'])) ? $members[$i]['person_name_family'] : '';
1146  $fullname = (isset($members[$i]['person_name_full'])) ? $members[$i]['person_name_full'] : '';
1147  $userresult->setNames($firstname, $lastname, $fullname);
1148 
1149  // Set the sourcedId
1150  if (isset($members[$i]['person_sourcedid'])) {
1151  $userresult->sourcedId = $members[$i]['person_sourcedid'];
1152  }
1153 
1154  // Set the user email
1155  $email = (isset($members[$i]['person_contact_email_primary'])) ? $members[$i]['person_contact_email_primary'] : '';
1156  $userresult->setEmail($email, $this->getPlatform()->defaultEmail);
1157 
1158  // Set the user roles
1159  if (isset($members[$i]['roles'])) {
1160  $userresult->roles = Tool::parseRoles($members[$i]['roles']);
1161  }
1162 
1163  // Set the user groups
1164  if (!isset($members[$i]['groups']['group'])) {
1165  $groups = array();
1166  } elseif (!isset($members[$i]['groups']['group'][0])) {
1167  $groups = array();
1168  $groups[0] = $members[$i]['groups']['group'];
1169  } else {
1170  $groups = $members[$i]['groups']['group'];
1171  }
1172  for ($j = 0; $j < count($groups); $j++) {
1173  $group = $groups[$j];
1174  if (isset($group['set'])) {
1175  $set_id = $group['set']['id'];
1176  if (!isset($this->groupSets[$set_id])) {
1177  $this->groupSets[$set_id] = array('title' => $group['set']['title'], 'groups' => array(),
1178  'num_members' => 0, 'num_staff' => 0, 'num_learners' => 0);
1179  }
1180  $this->groupSets[$set_id]['num_members']++;
1181  if ($userresult->isStaff()) {
1182  $this->groupSets[$set_id]['num_staff']++;
1183  }
1184  if ($userresult->isLearner()) {
1185  $this->groupSets[$set_id]['num_learners']++;
1186  }
1187  if (!in_array($group['id'], $this->groupSets[$set_id]['groups'])) {
1188  $this->groupSets[$set_id]['groups'][] = $group['id'];
1189  }
1190  $this->groups[$group['id']] = array('title' => $group['title'], 'set' => $set_id);
1191  } else {
1192  $this->groups[$group['id']] = array('title' => $group['title']);
1193  }
1194  $userresult->groups[] = $group['id'];
1195  }
1196  if (isset($members[$i]['lis_result_sourcedid'])) {
1197  $userresult->ltiResultSourcedId = $members[$i]['lis_result_sourcedid'];
1198  }
1199  $userResults[] = $userresult;
1200  }
1201  } else {
1202  $userResults = false;
1203  }
1204  $ok = $userResults !== false;
1205  }
1206  if (!$ok && $hasApiHook) {
1207  $className = $this->getApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getPlatform()->getFamilyCode());
1208  $hook = new $className($this);
1209  $userResults = $hook->getMemberships($withGroups);
1210  $ok = $userResults !== false;
1211  }
1212  if ($ok) {
1213  $oldUsers = $this->getUserResultSourcedIDs(true, Tool::ID_SCOPE_RESOURCE);
1214  foreach ($userResults as $userresult) {
1215  // If a result sourcedid is provided save the user
1216  if (!empty($userresult->ltiResultSourcedId)) {
1217  $userresult->save();
1218  }
1219  // Remove old user (if it exists)
1220  unset($oldUsers[$userresult->getId(Tool::ID_SCOPE_RESOURCE)]);
1221  }
1222  // Delete any old users which were not in the latest list from the platform
1223  foreach ($oldUsers as $id => $userresult) {
1224  $userresult->delete();
1225  }
1226  }
1227 
1228  return $userResults;
1229  }
1230 
1239  public function getUserResultSourcedIDs(bool $localOnly = false, int $idScope = null): array
1240  {
1241  return $this->getDataConnector()->getUserResultSourcedIDsResourceLink($this, $localOnly, $idScope);
1242  }
1243 
1249  public function getShares(): array
1250  {
1251  return $this->getDataConnector()->getSharesResourceLink($this);
1252  }
1253 
1261  public function getLineItems(string $resourceId = null, string $tag = null, int $limit = null)
1262  {
1263  $lineItems = false;
1264  $this->extRequest = '';
1265  $this->extRequestHeaders = '';
1266  $this->extResponse = '';
1267  $this->extResponseHeaders = '';
1268  $this->lastServiceRequest = null;
1269  $lineItemService = $this->getLineItemService();
1270  if (!empty($lineItemService)) {
1271  $lineItems = $lineItemService->getAll($this->ltiResourceLinkId, $resourceId, $tag, $limit);
1272  $http = $lineItemService->getHttpMessage();
1273  $this->extResponse = $http->response;
1274  $this->extResponseHeaders = $http->responseHeaders;
1275  $this->extRequest = $http->request;
1276  $this->extRequestHeaders = $http->requestHeaders;
1277  $this->lastServiceRequest = $http;
1278  }
1279 
1280  return $lineItems;
1281  }
1282 
1288  public function createLineItem(LineItem $lineItem): bool
1289  {
1290  $ok = false;
1291  $lineItemService = $this->getLineItemService();
1292  if (!empty($lineItemService)) {
1293  $lineItem->ltiResourceLinkId = $this->ltiResourceLinkId;
1294  $ok = $lineItemService->createLineItem($lineItem);
1295  }
1296 
1297  return $ok;
1298  }
1299 
1305  public function getOutcomes(int $limit = null)
1306  {
1307  $outcomes = false;
1308  $this->extRequest = '';
1309  $this->extRequestHeaders = '';
1310  $this->extResponse = '';
1311  $this->extResponseHeaders = '';
1312  $this->lastServiceRequest = null;
1313  $url = $this->getSetting('custom_lineitem_url');
1314  if (!empty($url)) {
1315  $resultService = new Service\Result($this->getPlatform(), $url);
1316  $outcomes = $resultService->getAll($limit);
1317  $http = $resultService->getHttpMessage();
1318  $this->extResponse = $http->response;
1319  $this->extResponseHeaders = $http->responseHeaders;
1320  $this->extRequest = $http->request;
1321  $this->extRequestHeaders = $http->requestHeaders;
1322  $this->lastServiceRequest = $http;
1323  }
1324 
1325  return $outcomes;
1326  }
1327 
1335  public function doAssessmentControlAction(AssessmentControlAction $assessmentControlAction, User $user, int $attemptNumber)
1336  {
1337  $status = false;
1338  $this->extRequest = '';
1339  $this->extRequestHeaders = '';
1340  $this->extResponse = '';
1341  $this->extResponseHeaders = '';
1342  $this->lastServiceRequest = null;
1343  $url = $this->getSetting('custom_ap_acs_url');
1344  if (!empty($url)) {
1345  $assessmentControlService = new Service\AssessmentControl($this, $url);
1346  $status = $assessmentControlService->submitAction($assessmentControlAction, $user, $attemptNumber);
1347  $http = $assessmentControlService->getHttpMessage();
1348  $this->extResponse = $http->response;
1349  $this->extResponseHeaders = $http->responseHeaders;
1350  $this->extRequest = $http->request;
1351  $this->extRequestHeaders = $http->requestHeaders;
1352  $this->lastServiceRequest = $http;
1353  }
1354 
1355  return $status;
1356  }
1357 
1358 // /**
1359 // * Class constructor from consumer.
1360 // *
1361 // * @deprecated Use fromPlatform() instead
1362 // * @see ResourceLink::fromPlatform()
1363 // *
1364 // * @param ToolConsumer $consumer Consumer object
1365 // * @param string $ltiResourceLinkId Resource link ID value
1366 // * @param string $tempId Temporary Resource link ID value (optional, default is null)
1367 // *
1368 // * @return ResourceLink
1369 // */
1370 // public static function fromConsumer($consumer, $ltiResourceLinkId, $tempId = null)
1371 // {
1372 // Util::logDebug('Method ceLTIc\LTI\ResourceLink::fromConsumer() has been deprecated; please use ceLTIc\LTI\ResourceLink::fromPlatform() instead.',
1373 // true);
1374 // return self::fromPlatform($consumer, $ltiResourceLinkId, $tempId);
1375 // }
1376 
1384  public static function fromPlatform(Platform $platform, string $ltiResourceLinkId, string $tempId = null): ResourceLink
1385  {
1386  $resourceLink = new ResourceLink();
1387  $resourceLink->platform = $platform;
1388  $resourceLink->dataConnector = $platform->getDataConnector();
1389  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1390  if (!empty($ltiResourceLinkId)) {
1391  $resourceLink->load();
1392  if (is_null($resourceLink->id) && !empty($tempId)) {
1393  $resourceLink->ltiResourceLinkId = $tempId;
1394  $resourceLink->load();
1395  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1396  }
1397  }
1398 
1399  return $resourceLink;
1400  }
1401 
1409  public static function fromContext(Context $context, string $ltiResourceLinkId, string $tempId = null): ResourceLink
1410  {
1411  $resourceLink = new ResourceLink();
1412  $resourceLink->setContext($context);
1413  $resourceLink->dataConnector = $context->getDataConnector();
1414  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1415  if (!empty($ltiResourceLinkId)) {
1416  $resourceLink->load();
1417  if (is_null($resourceLink->id) && !empty($tempId)) {
1418  $resourceLink->ltiResourceLinkId = $tempId;
1419  $resourceLink->load();
1420  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1421  }
1422  $resourceLink->setContext($context); // Ensure context remains set
1423  }
1424 
1425  return $resourceLink;
1426  }
1427 
1434  public static function fromRecordId(int $id, DataConnector $dataConnector): ResourceLink
1435  {
1436  $resourceLink = new ResourceLink();
1437  $resourceLink->dataConnector = $dataConnector;
1438  $resourceLink->load($id);
1439 
1440  return $resourceLink;
1441  }
1442 
1443  ###
1444  ### PRIVATE METHODS
1445  ###
1446 
1452  private function load(int $id = null): bool
1453  {
1454  $this->initialize();
1455  $this->id = $id;
1456 
1457  return $this->getDataConnector()->loadResourceLink($this);
1458  }
1459 
1466  private function checkValueType(Outcome $ltiOutcome, array $supportedTypes = null): bool
1467  {
1468  if (empty($supportedTypes)) {
1469  $supportedTypes = explode(
1470  ',',
1471  str_replace(' ', '', strtolower($this->getSetting('ext_ims_lis_resultvalue_sourcedids', self::EXT_TYPE_DECIMAL)))
1472  );
1473  }
1474  $type = $ltiOutcome->type;
1475  $value = $ltiOutcome->getValue();
1476  // Check whether the type is supported or there is no value
1477  $ok = in_array($type, $supportedTypes) || empty($value);
1478  if (!$ok) {
1479  // Convert numeric values to decimal
1480  if ($type === self::EXT_TYPE_PERCENTAGE) {
1481  if (substr($value, -1) === '%') {
1482  $value = substr($value, 0, -1);
1483  }
1484  $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1485  if ($ok) {
1486  $ltiOutcome->setValue($value / 100);
1487  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1488  }
1489  } elseif ($type === self::EXT_TYPE_RATIO) {
1490  $parts = explode('/', $value, 2);
1491  $ok = (count($parts) === 2) && is_numeric($parts[0]) && is_numeric($parts[1]) && ($parts[0] >= 0) && ($parts[1] > 0);
1492  if ($ok) {
1493  $ltiOutcome->setValue($parts[0] / $parts[1]);
1494  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1495  }
1496  // Convert letter_af to letter_af_plus or text
1497  } elseif ($type === self::EXT_TYPE_LETTER_AF) {
1498  if (in_array(self::EXT_TYPE_LETTER_AF_PLUS, $supportedTypes)) {
1499  $ok = true;
1500  $ltiOutcome->type = self::EXT_TYPE_LETTER_AF_PLUS;
1501  } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1502  $ok = true;
1503  $ltiOutcome->type = self::EXT_TYPE_TEXT;
1504  }
1505  // Convert letter_af_plus to letter_af or text
1506  } elseif ($type === self::EXT_TYPE_LETTER_AF_PLUS) {
1507  if (in_array(self::EXT_TYPE_LETTER_AF, $supportedTypes) && (strlen($value) === 1)) {
1508  $ok = true;
1509  $ltiOutcome->type = self::EXT_TYPE_LETTER_AF;
1510  } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1511  $ok = true;
1512  $ltiOutcome->type = self::EXT_TYPE_TEXT;
1513  }
1514  // Convert text to decimal
1515  } elseif ($type === self::EXT_TYPE_TEXT) {
1516  $ok = is_numeric($value) && ($value >= 0) && ($value <= 1);
1517  if ($ok) {
1518  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1519  } elseif (substr($value, -1) === '%') {
1520  $value = substr($value, 0, -1);
1521  $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1522  if ($ok) {
1523  if (in_array(self::EXT_TYPE_PERCENTAGE, $supportedTypes)) {
1524  $ltiOutcome->type = self::EXT_TYPE_PERCENTAGE;
1525  } else {
1526  $ltiOutcome->setValue($value / 100);
1527  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1528  }
1529  }
1530  }
1531  }
1532  }
1533 
1534  return $ok;
1535  }
1536 
1545  private function doService(string $type, string $url, array $params, string $scope): bool
1546  {
1547  $ok = false;
1548  $this->extRequest = '';
1549  $this->extRequestHeaders = '';
1550  $this->extResponse = '';
1551  $this->extResponseHeaders = '';
1552  $this->lastServiceRequest = null;
1553  if (!empty($url)) {
1554  $params['lti_version'] = $this->getPlatform()->ltiVersion;
1555  $params['lti_message_type'] = $type;
1556  $retry = false;
1557  $newToken = false;
1558  if (!$this->getPlatform()->useOAuth1()) {
1559  $accessToken = $this->platform->getAccessToken();
1560  $retry = true;
1561  if (empty($accessToken)) {
1562  $accessToken = new AccessToken($this->platform);
1563  $this->platform->setAccessToken($accessToken);
1564  }
1565  if (!$accessToken->hasScope($scope) && (empty(Tool::$defaultTool) || !in_array(
1566  $scope,
1567  Tool::$defaultTool->requiredScopes
1568  ))) {
1569  $accessToken->expires = time();
1570  $accessToken->get($scope, true);
1571  $this->platform->setAccessToken($accessToken);
1572  $newToken = true;
1573  }
1574  }
1575  do {
1576  // Add message signature
1577  $signed = $this->getPlatform()->addSignature($url, $params, 'POST', 'application/x-www-form-urlencoded');
1578  // Connect to platform
1579  if (is_array($signed)) {
1580  $http = new HttpMessage($url, 'POST', $signed);
1581  } else {
1582  $http = new HttpMessage($url, 'POST', $params, $signed);
1583  }
1584  if ($http->send()) {
1585  // Parse XML response
1586  $this->extResponse = $http->response;
1587  $this->extResponseHeaders = $http->responseHeaders;
1588  try {
1589  $this->extDoc = new DOMDocument();
1590  $this->extDoc->loadXML($http->response);
1591  $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement);
1592  if (isset($this->extNodes['statusinfo']['codemajor']) && ($this->extNodes['statusinfo']['codemajor'] === 'Success')) {
1593  $ok = true;
1594  }
1595  } catch (\Exception $e) {
1596  }
1597  }
1598  $retry = $retry && !$newToken && !$ok;
1599  if ($retry) { // Obtain a new access token just for the required scope
1600  $accessToken = $this->platform->getAccessToken();
1601  $accessToken->expires = time();
1602  $accessToken->get($scope, true);
1603  $this->platform->setAccessToken($accessToken);
1604  $newToken = true;
1605  }
1606  } while ($retry);
1607  $this->extRequest = $http->request;
1608  $this->extRequestHeaders = $http->requestHeaders;
1609  $this->lastServiceRequest = $http;
1610  }
1611 
1612  return $ok;
1613  }
1614 
1621  private function doResultService(UserResult $userResult, string $url): ?Outcome
1622  {
1623  $outcome = null;
1624  $this->extRequest = '';
1625  $this->extRequestHeaders = '';
1626  $this->extResponse = '';
1627  $this->extResponseHeaders = '';
1628  $this->lastServiceRequest = null;
1629  if (!empty($url)) {
1630  $resultService = new Service\Result($this->getPlatform(), $url);
1631  $outcome = $resultService->get($userResult);
1632  $http = $resultService->getHttpMessage();
1633  $this->extResponse = $http->response;
1634  $this->extResponseHeaders = $http->responseHeaders;
1635  $this->extRequest = $http->request;
1636  $this->extRequestHeaders = $http->requestHeaders;
1637  $this->lastServiceRequest = $http;
1638  }
1639 
1640  return $outcome;
1641  }
1642 
1650  private function doScoreService(Outcome $ltiOutcome, UserResult $userResult, string $url): bool
1651  {
1652  $ok = false;
1653  $this->extRequest = '';
1654  $this->extRequestHeaders = '';
1655  $this->extResponse = '';
1656  $this->extResponseHeaders = '';
1657  $this->lastServiceRequest = null;
1658  if (!empty($url)) {
1659  $scoreService = new Service\Score($this->getPlatform(), $url);
1660  $scoreService->submit($ltiOutcome, $userResult);
1661  $http = $scoreService->getHttpMessage();
1662  $this->extResponse = $http->response;
1663  $this->extResponseHeaders = $http->responseHeaders;
1664  $ok = $http->ok;
1665  $this->extRequest = $http->request;
1666  $this->extRequestHeaders = $http->requestHeaders;
1667  $this->lastServiceRequest = $http;
1668  }
1669 
1670  return $ok;
1671  }
1672 
1680  private function doLTI11Service(string $type, string $url, string $xml): bool
1681  {
1682  $ok = false;
1683  $this->extRequest = '';
1684  $this->extRequestHeaders = '';
1685  $this->extResponse = '';
1686  $this->extResponseHeaders = '';
1687  $this->lastServiceRequest = null;
1688  if (!empty($url)) {
1689  $id = uniqid();
1690  $xmlRequest = <<< EOD
1691 <?xml version = "1.0" encoding = "UTF-8"?>
1692 <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
1693  <imsx_POXHeader>
1694  <imsx_POXRequestHeaderInfo>
1695  <imsx_version>V1.0</imsx_version>
1696  <imsx_messageIdentifier>{$id}</imsx_messageIdentifier>
1697  </imsx_POXRequestHeaderInfo>
1698  </imsx_POXHeader>
1699  <imsx_POXBody>
1700  <{$type}Request>
1701 {$xml}
1702  </{$type}Request>
1703  </imsx_POXBody>
1704 </imsx_POXEnvelopeRequest>
1705 EOD;
1706  $scope = 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome';
1707  $retry = false;
1708  $newToken = false;
1709  if (!$this->getPlatform()->useOAuth1()) {
1710  $accessToken = $this->platform->getAccessToken();
1711  $retry = true;
1712  if (empty($accessToken)) {
1713  $accessToken = new AccessToken($this->platform);
1714  $this->platform->setAccessToken($accessToken);
1715  }
1716  if (!$accessToken->hasScope($scope) && (empty(Tool::$defaultTool) || !in_array(
1717  $scope,
1718  Tool::$defaultTool->requiredScopes
1719  ))) {
1720  $accessToken->expires = time();
1721  $accessToken->get($scope, true);
1722  $this->platform->setAccessToken($accessToken);
1723  $newToken = true;
1724  }
1725  }
1726  do {
1727  // Add message signature
1728  $header = $this->getPlatform()->addSignature($url, $xmlRequest, 'POST', 'application/xml');
1729  // Connect to platform
1730  $http = new \ILIAS\LTI\ToolProvider\Http\HttpMessage($url, 'POST', $xmlRequest, $header);
1731  if ($http->send()) {
1732  // Parse XML response
1733  $this->extResponse = $http->response;
1734  $this->extResponseHeaders = $http->responseHeaders;
1735  try {
1736  $this->extDoc = new DOMDocument();
1737  $this->extDoc->loadXML($http->response);
1738  $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement);
1739  if (isset($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor']) &&
1740  ($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor'] === 'success')) {
1741  $ok = true;
1742  }
1743  } catch (\Exception $e) {
1744  }
1745  }
1746  $retry = $retry && !$newToken && !$ok;
1747  if ($retry) { // Obtain a new access token just for the required scope
1748  $accessToken = $this->platform->getAccessToken();
1749  $accessToken->expires = time();
1750  $accessToken->get($scope, true);
1751  $this->platform->setAccessToken($accessToken);
1752  $newToken = true;
1753  }
1754  } while ($retry);
1755  $this->extRequest = $http->request;
1756  $this->extRequestHeaders = $http->requestHeaders;
1757  $this->lastServiceRequest = $http;
1758  }
1759 
1760  return $ok;
1761  }
1762 
1768  private function getLineItemService()
1769  {
1770  $url = $this->getSetting('custom_lineitems_url');
1771  if (!empty($url)) {
1772  $lineItemService = new Service\LineItem($this->getPlatform(), $url);
1773  } else {
1774  $lineItemService = false;
1775  }
1776 
1777  return $lineItemService;
1778  }
1779 
1785  private function domnodeToArray($node)
1786  {
1787  $output = array();
1788  switch ($node->nodeType) {
1789  case XML_CDATA_SECTION_NODE:
1790  case XML_TEXT_NODE:
1791  $output = trim($node->textContent);
1792  break;
1793  case XML_ELEMENT_NODE:
1794  for ($i = 0; $i < $node->childNodes->length; $i++) {
1795  $child = $node->childNodes->item($i);
1796  $v = $this->domnodeToArray($child);
1797  if (isset($child->tagName)) {
1798  $output[$child->tagName][] = $v;
1799  } else {
1800  $s = (string) $v;
1801  if (!empty($s)) {
1802  $output = $s;
1803  }
1804  }
1805  }
1806  if (is_array($output)) {
1807  if ($node->hasAttributes()) {
1808  foreach ($node->attributes as $attrNode) {
1809  $output['@attributes'][$attrNode->name] = (string) $attrNode->value;
1810  }
1811  }
1812  foreach ($output as $t => $v) {
1813  if (is_array($v) && (count($v) === 1) && ($t !== '@attributes')) {
1814  $output[$t] = $v[0];
1815  }
1816  }
1817  }
1818  break;
1819  }
1820 
1821  return $output;
1822  }
1823 }
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
$type
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
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
if($format !==null) $name
Definition: metadata.php:247
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.
setValue(string $value)
Set the outcome value.
Definition: Outcome.php:167
$format
Definition: metadata.php:235
$xml
Definition: metadata.php:351
$http
Definition: raiseError.php:7
static string $SCOPE
Access scope.
Definition: Result.php:36
$comment
Definition: buildRTE.php:72
if($orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:302
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...
$url
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
$response
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
$i
Definition: metadata.php:41
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