198 private $platform =
null;
205 private $platformId =
null;
212 private $context =
null;
219 private $contextId =
null;
226 private $settings =
null;
233 private $settingsChanged =
false;
240 private $extDoc =
null;
247 private $extNodes =
null;
254 private $dataConnector =
null;
270 $this->settings = array();
271 $this->groupSets =
null;
272 $this->groups =
null;
273 $this->primaryResourceLinkId =
null;
274 $this->shareApproved =
null;
275 $this->created =
null;
276 $this->updated =
null;
294 public function save()
298 $this->settingsChanged =
false;
309 public function delete()
324 Util::logDebug(
'Method ceLTIc\LTI\ResourceLink::getConsumer() has been deprecated; please use ceLTIc\LTI\ResourceLink::getPlatform() instead.',
339 Util::logDebug(
'Method ceLTIc\LTI\ResourceLink::getConsumerId() has been deprecated; please use ceLTIc\LTI\ResourceLink::getPlatformId() instead.',
354 Util::logDebug(
'Method ceLTIc\LTI\ResourceLink::setConsumerId() has been deprecated; please use ceLTIc\LTI\ResourceLink::setPlatformId() instead.',
366 if (is_null($this->platform)) {
367 if (!is_null($this->context) || !is_null($this->contextId)) {
368 $this->platform = $this->
getContext()->getPlatform();
374 return $this->platform;
384 return $this->platformId;
394 $this->platform =
null;
395 $this->platformId = $platformId;
405 if (is_null($this->context) && !is_null($this->contextId)) {
409 return $this->context;
419 if (is_null($this->contextId) && !is_null($this->context)) {
420 $this->contextId = $this->context->getRecordId();
423 return $this->contextId;
433 $this->context = $context;
434 $this->contextId = $context->getRecordId();
444 if ($this->contextId !== $contextId) {
445 $this->context =
null;
446 $this->contextId = $contextId;
465 public function getId()
497 if (empty($this->dataConnector)) {
499 if (!empty($this->platform)) {
500 $this->dataConnector = $this->platform->getDataConnector();
504 return $this->dataConnector;
515 public function getSetting($name, $default =
'')
517 if (array_key_exists($name, $this->settings)) {
518 $value = $this->settings[$name];
532 public function setSetting($name, $value =
null)
535 if ($value !== $old_value) {
536 if (!empty($value)) {
537 $this->settings[$name] = $value;
539 unset($this->settings[$name]);
541 $this->settingsChanged =
true;
552 return $this->settings;
562 $this->settings = $settings;
572 if ($this->settingsChanged) {
588 $has = !empty($this->
getSetting(
'ext_ims_lis_basic_outcome_url')) || !empty($this->
getSetting(
'lis_outcome_service_url'));
589 if (!$has && !empty($this->
getSetting(
'custom_lineitem_url')) && !empty($this->
getSetting(
'custom_ags_scopes'))) {
590 $scopes = explode(
',', $this->
getSetting(
'custom_ags_scopes'));
594 $has = self::hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->
getPlatform()->getFamilyCode(), $this);
611 $has = !empty($this->
getSetting(
'custom_link_memberships_url'));
614 $has = !empty($this->
getSetting(
'ext_ims_lis_memberships_url'));
617 $has = self::hasConfiguredApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->
getPlatform()->getFamilyCode(), $this);
630 $url = $this->
getSetting(
'ext_ims_lti_tool_setting_url');
643 if (!empty($this->
getSetting(
'custom_ags_scopes'))) {
644 $scopes = explode(
',', $this->
getSetting(
'custom_ags_scopes'));
645 if (in_array(
Service\LineItem::$SCOPE, $scopes) || in_array(
Service\LineItem::$SCOPE_READONLY, $scopes)) {
646 $has = !empty($this->
getSetting(
'custom_lineitems_url'));
661 if (!empty($this->
getSetting(
'custom_ags_scopes'))) {
662 $scopes = explode(
',', $this->
getSetting(
'custom_ags_scopes'));
664 $has = !empty($this->
getSetting(
'custom_lineitem_url'));
679 if (!empty($this->
getSetting(
'custom_ags_scopes'))) {
680 $scopes = explode(
',', $this->
getSetting(
'custom_ags_scopes'));
682 $has = !empty($this->
getSetting(
'custom_lineitem_url'));
696 $url = $this->
getSetting(
'custom_ap_acs_url');
713 $this->extResponse =
'';
715 $sourceResourceLink = $userResult->getResourceLink();
716 $sourcedId = $userResult->ltiResultSourcedId;
719 $urlAGS = $sourceResourceLink->getSetting(
'custom_lineitem_url');
720 $urlLTI11 = $sourceResourceLink->getSetting(
'lis_outcome_service_url');
721 $urlExt = $sourceResourceLink->getSetting(
'ext_ims_lis_basic_outcome_url');
723 if (!empty($urlAGS)) {
724 if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL) && $sourceResourceLink->hasResultService()) {
725 $ltiOutcome = $this->doResultService($userResult, $urlAGS);
726 $ok = !empty($ltiOutcome);
727 } elseif ((($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL)) && $sourceResourceLink->hasScoreService()) ||
728 ($action === self::EXT_DELETE)) {
729 if ($action === self::EXT_DELETE) {
730 $ltiOutcome->setValue(
null);
731 $ltiOutcome->activityProgress =
'Initialized';
732 $ltiOutcome->gradingProgress =
'NotReady';
734 $ok = $this->doScoreService($ltiOutcome, $userResult, $urlAGS);
737 if (!$ok && is_null($ltiOutcome->getValue())) {
738 $ltiOutcome->setValue(
'');
740 if (!$ok && !empty($urlLTI11)) {
742 $outcome = $ltiOutcome->getValue();
743 if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
745 } elseif (($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
746 $do =
'replaceResult';
747 if (($ltiOutcome->getPointsPossible() <> 1) && ($ltiOutcome->getPointsPossible() > 0)) {
748 $outcome = $outcome / $ltiOutcome->getPointsPossible();
750 } elseif ($action === self::EXT_DELETE) {
751 $do =
'deleteResult';
755 if ($action === self::EXT_WRITE) {
756 $comment = (empty($ltiOutcome->comment)) ?
'' : trim($ltiOutcome->comment);
757 if (!empty($comment) && !empty($sourceResourceLink->getSetting(
'ext_outcome_data_values_accepted'))) {
758 $resultDataTypes = explode(
',', $sourceResourceLink->getSetting(
'ext_outcome_data_values_accepted'));
759 $resultDataType =
'';
760 if (count($resultDataTypes) === 1) {
761 $resultDataType = $resultDataTypes[0];
762 } elseif (count($resultDataTypes) > 1) {
763 $isUrl = (strpos($comment,
'http://') === 0) || (strpos($comment,
'https://') === 0);
764 if ($isUrl && in_array(
'ltiLaunchUrl', $resultDataTypes)) {
765 $resultDataType =
'ltiLaunchUrl';
766 } elseif ($isUrl && in_array(
'url', $resultDataTypes)) {
767 $resultDataType =
'url';
768 } elseif (in_array(
'text', $resultDataTypes)) {
769 $resultDataType =
'text';
772 if (!empty($resultDataType)) {
776 <{$resultDataType}>{$comment}</{$resultDataType}>
785 <language>{$ltiOutcome->language}</language>
786 <textString>{$outcome}</textString>
791 $sourcedId = htmlentities($sourcedId);
795 <sourcedId>{$sourcedId}</sourcedId>
799 if ($this->doLTI11Service($do, $urlLTI11, $xml)) {
802 if (!isset($this->extNodes[
'imsx_POXBody'][
"{$do}Response"][
'result'][
'resultScore'][
'textString'])) {
805 $ltiOutcome->setValue($this->extNodes[
'imsx_POXBody'][
"{$do}Response"][
'result'][
'resultScore'][
'textString']);
815 if (!$ok && !empty($urlExt)) {
817 $outcome = $ltiOutcome->getValue();
818 if (($action === self::EXT_READ) && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
819 $do =
'basic-lis-readresult';
820 } elseif (($action === self::EXT_WRITE) && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
821 $do =
'basic-lis-updateresult';
822 if (($ltiOutcome->getPointsPossible() <> 1) && ($ltiOutcome->getPointsPossible() > 0)) {
823 $outcome = $outcome / $ltiOutcome->getPointsPossible();
825 } elseif ($action === self::EXT_DELETE) {
826 $do =
'basic-lis-deleteresult';
830 $params[
'sourcedid'] = $sourcedId;
831 $params[
'result_resultscore_textstring'] = $outcome;
832 if (!empty($ltiOutcome->language)) {
833 $params[
'result_resultscore_language'] = $ltiOutcome->language;
835 if (!empty($ltiOutcome->status)) {
836 $params[
'result_statusofresult'] = $ltiOutcome->status;
838 if (!empty($ltiOutcome->date)) {
839 $params[
'result_date'] = $ltiOutcome->date;
841 if (!empty($ltiOutcome->type)) {
842 $params[
'result_resultvaluesourcedid'] = $ltiOutcome->type;
844 if (!empty($ltiOutcome->dataSource)) {
845 $params[
'result_datasource'] = $ltiOutcome->dataSource;
847 if ($this->doService($do, $urlExt, $params,
'https://purl.imsglobal.org/spec/lti-ext/scope/outcomes')) {
850 if (isset($this->extNodes[
'result'][
'resultscore'][
'textstring'])) {
851 $ltiOutcome->setValue($this->extNodes[
'result'][
'resultscore'][
'textstring']);
861 if ((!$ok) && $this->hasConfiguredApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->
getPlatform()->getFamilyCode(), $this)) {
862 $className = $this->getApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->
getPlatform()->getFamilyCode());
863 $hook =
new $className($this);
864 $response = $hook->doOutcomesService($action, $ltiOutcome, $userResult);
865 if ($response !==
false) {
867 if ($action === self::EXT_READ) {
868 $ltiOutcome->setValue($response);
890 Util::logDebug(
'Method ceLTIc\LTI\ResourceLink::doMembershipsService() has been deprecated; please use ceLTIc\LTI\ResourceLink::getMemberships() instead.',
906 $this->extResponse =
'';
909 $do =
'basic-lti-loadsetting';
912 $do =
'basic-lti-savesetting';
915 $do =
'basic-lti-deletesetting';
920 $url = $this->
getSetting(
'ext_ims_lti_tool_setting_url');
922 $params[
'id'] = $this->
getSetting(
'ext_ims_lti_tool_setting_id');
923 if (is_null($value)) {
926 $params[
'setting'] = $value;
928 if ($this->doService($do, $url, $params,
'https://purl.imsglobal.org/spec/lti-ext/scope/setting')) {
931 if (isset($this->extNodes[
'setting'][
'value'])) {
932 $response = $this->extNodes[
'setting'][
'value'];
933 if (is_array($response)) {
939 $this->
setSetting(
'ext_ims_lti_tool_setting', $value);
960 $has = !empty($this->
getSetting(
'custom_link_setting_url'));
962 $has = self::hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->
getPlatform()->getFamilyCode(), $this);
979 if (!empty($this->
getSetting(
'custom_link_setting_url'))) {
980 $url = $this->
getSetting(
'custom_link_setting_url');
982 $settings = $service->get($mode);
983 $this->lastServiceRequest = $service->getHttpMessage();
984 $ok = $settings !==
false;
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);
1005 if (!empty($this->
getSetting(
'custom_link_setting_url'))) {
1006 $url = $this->
getSetting(
'custom_link_setting_url');
1008 $ok = $service->set($settings);
1009 $this->lastServiceRequest = $service->getHttpMessage();
1011 if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->
getPlatform()->getFamilyCode(), $this)) {
1012 $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->
getPlatform()->getFamilyCode());
1013 $hook =
new $className($this);
1014 $ok = $hook->setToolSettings($settings);
1030 Util::logDebug(
'Method ceLTIc\LTI\ResourceLink::hasMembershipService() has been deprecated; please use ceLTIc\LTI\ResourceLink::hasMembershipsService() instead.',
1045 Util::logDebug(
'Method ceLTIc\LTI\ResourceLink::getMembership() has been deprecated; please use ceLTIc\LTI\ResourceLink::getMemberships() instead.',
1060 $userResults = array();
1061 $hasLtiLinkService = !empty($this->
getSetting(
'custom_link_memberships_url'));
1062 $hasLtiContextService = !empty($this->
getContextId()) &&
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)) {
1069 $url = $this->
getContext()->getSetting(
'custom_context_memberships_v2_url');
1070 $format = Service\Membership::MEDIA_TYPE_MEMBERSHIPS_NRPS;
1072 $url = $this->
getContext()->getSetting(
'custom_context_memberships_url');
1073 $format = Service\Membership::MEDIA_TYPE_MEMBERSHIPS_V1;
1077 $userResults = $service->get();
1079 $userResults = $service->getWithGroups();
1081 $this->lastServiceRequest = $service->getHttpMessage();
1082 $ok = $userResults !==
false;
1083 } elseif ($hasLtiLinkService) {
1086 $url = $this->
getSetting(
'custom_link_memberships_url');
1087 $format = Service\Membership::MEDIA_TYPE_MEMBERSHIPS_V1;
1090 $userResults = $service->get();
1092 $userResults = $service->getWithGroups();
1094 $this->lastServiceRequest = $service->getHttpMessage();
1096 $ok = $userResults !==
false;
1098 if (!$ok && $hasExtService) {
1099 $this->extResponse =
'';
1100 $url = $this->
getSetting(
'ext_ims_lis_memberships_url');
1102 $params[
'id'] = $this->
getSetting(
'ext_ims_lis_memberships_id');
1104 $ok = $this->doService(
'basic-lis-readmembershipsforcontextwithgroups', $url, $params,
1105 'https://purl.imsglobal.org/spec/lti-ext/scope/memberships');
1108 $ok = $this->doService(
'basic-lis-readmembershipsforcontext', $url, $params,
1109 'https://purl.imsglobal.org/spec/lti-ext/scope/memberships');
1112 $this->groupSets = array();
1113 $this->groups = array();
1114 if (isset($this->extNodes[
'memberships'])) {
1115 $memberships = $this->extNodes[
'memberships'];
1116 } elseif (isset($this->extNodes[
'members'])) {
1117 $memberships = $this->extNodes[
'members'];
1123 if (!isset($memberships[
'member'])) {
1125 } elseif (!isset($memberships[
'member'][0])) {
1127 $members[0] = $memberships[
'member'];
1129 $members = $memberships[
'member'];
1132 for ($i = 0; $i < count($members); $i++) {
1137 $firstname = (isset($members[$i][
'person_name_given'])) ? $members[$i][
'person_name_given'] :
'';
1138 $middlename = (isset($members[$i][
'person_name_middle'])) ? $members[$i][
'person_name_middle'] :
'';
1139 $lastname = (isset($members[$i][
'person_name_family'])) ? $members[$i][
'person_name_family'] :
'';
1140 $fullname = (isset($members[$i][
'person_name_full'])) ? $members[$i][
'person_name_full'] :
'';
1141 $userresult->setNames($firstname, $lastname, $fullname);
1144 if (isset($members[$i][
'person_sourcedid'])) {
1145 $userresult->sourcedId = $members[$i][
'person_sourcedid'];
1149 $email = (isset($members[$i][
'person_contact_email_primary'])) ? $members[$i][
'person_contact_email_primary'] :
'';
1150 $userresult->setEmail($email, $this->
getPlatform()->defaultEmail);
1153 if (isset($members[$i][
'roles'])) {
1158 if (!isset($members[$i][
'groups'][
'group'])) {
1160 } elseif (!isset($members[$i][
'groups'][
'group'][0])) {
1162 $groups[0] = $members[$i][
'groups'][
'group'];
1164 $groups = $members[$i][
'groups'][
'group'];
1166 for ($j = 0; $j < count(
$groups); $j++) {
1168 if (isset($group[
'set'])) {
1169 $set_id = $group[
'set'][
'id'];
1170 if (!isset($this->groupSets[$set_id])) {
1171 $this->groupSets[$set_id] = array(
'title' => $group[
'set'][
'title'],
'groups' => array(),
1172 'num_members' => 0,
'num_staff' => 0,
'num_learners' => 0);
1174 $this->groupSets[$set_id][
'num_members']++;
1175 if ($userresult->isStaff()) {
1176 $this->groupSets[$set_id][
'num_staff']++;
1178 if ($userresult->isLearner()) {
1179 $this->groupSets[$set_id][
'num_learners']++;
1181 if (!in_array($group[
'id'], $this->groupSets[$set_id][
'groups'])) {
1182 $this->groupSets[$set_id][
'groups'][] = $group[
'id'];
1184 $this->groups[$group[
'id']] = array(
'title' => $group[
'title'],
'set' => $set_id);
1186 $this->groups[$group[
'id']] = array(
'title' => $group[
'title']);
1188 $userresult->groups[] = $group[
'id'];
1190 if (isset($members[$i][
'lis_result_sourcedid'])) {
1191 $userresult->ltiResultSourcedId = $members[$i][
'lis_result_sourcedid'];
1193 $userResults[] = $userresult;
1196 $userResults =
false;
1198 $ok = $userResults !==
false;
1200 if (!$ok && $hasApiHook) {
1201 $className = $this->getApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->
getPlatform()->getFamilyCode());
1202 $hook =
new $className($this);
1203 $userResults = $hook->getMemberships($withGroups);
1204 $ok = $userResults !==
false;
1208 foreach ($userResults as $userresult) {
1210 if (!empty($userresult->ltiResultSourcedId)) {
1211 $userresult->save();
1217 foreach ($oldUsers as $id => $userresult) {
1218 $userresult->delete();
1222 return $userResults;
1238 return $this->
getDataConnector()->getUserResultSourcedIDsResourceLink($this, $localOnly, $idScope);
1260 public function getLineItems($resourceId =
null, $tag =
null, $limit =
null)
1263 $this->extRequest =
'';
1264 $this->extRequestHeaders =
'';
1265 $this->extResponse =
'';
1266 $this->extResponseHeaders =
'';
1267 $this->lastServiceRequest =
null;
1268 $lineItemService = $this->getLineItemService();
1269 if (!empty($lineItemService)) {
1270 $lineItems = $lineItemService->getAll($this->ltiResourceLinkId, $resourceId, $tag, $limit);
1271 $http = $lineItemService->getHttpMessage();
1272 $this->extResponse = $http->response;
1273 $this->extResponseHeaders = $http->responseHeaders;
1274 $this->extRequest = $http->request;
1275 $this->extRequestHeaders = $http->requestHeaders;
1276 $this->lastServiceRequest = $http;
1292 $lineItemService = $this->getLineItemService();
1293 if (!empty($lineItemService)) {
1295 $ok = $lineItemService->createLineItem($lineItem);
1311 $this->extRequest =
'';
1312 $this->extRequestHeaders =
'';
1313 $this->extResponse =
'';
1314 $this->extResponseHeaders =
'';
1315 $this->lastServiceRequest =
null;
1316 $url = $this->
getSetting(
'custom_lineitem_url');
1319 $outcomes = $resultService->getAll($limit);
1320 $http = $resultService->getHttpMessage();
1321 $this->extResponse = $http->response;
1322 $this->extResponseHeaders = $http->responseHeaders;
1323 $this->extRequest = $http->request;
1324 $this->extRequestHeaders = $http->requestHeaders;
1325 $this->lastServiceRequest = $http;
1343 $this->extRequest =
'';
1344 $this->extRequestHeaders =
'';
1345 $this->extResponse =
'';
1346 $this->extResponseHeaders =
'';
1347 $this->lastServiceRequest =
null;
1348 $url = $this->
getSetting(
'custom_ap_acs_url');
1351 $status = $assessmentControlService->submitAction($assessmentControlAction, $user, $attemptNumber);
1352 $http = $assessmentControlService->getHttpMessage();
1353 $this->extResponse = $http->response;
1354 $this->extResponseHeaders = $http->responseHeaders;
1355 $this->extRequest = $http->request;
1356 $this->extRequestHeaders = $http->requestHeaders;
1357 $this->lastServiceRequest = $http;
1377 Util::logDebug(
'Method ceLTIc\LTI\ResourceLink::fromConsumer() has been deprecated; please use ceLTIc\LTI\ResourceLink::fromPlatform() instead.',
1394 $resourceLink->platform = $platform;
1395 $resourceLink->dataConnector = $platform->getDataConnector();
1398 $resourceLink->load();
1399 if (is_null($resourceLink->id) && !empty($tempId)) {
1400 $resourceLink->ltiResourceLinkId = $tempId;
1401 $resourceLink->load();
1406 return $resourceLink;
1421 $resourceLink->setContext($context);
1422 $resourceLink->dataConnector = $context->getDataConnector();
1425 $resourceLink->load();
1426 if (is_null($resourceLink->id) && !empty($tempId)) {
1427 $resourceLink->ltiResourceLinkId = $tempId;
1428 $resourceLink->load();
1431 $resourceLink->setContext($context);
1434 return $resourceLink;
1445 public static function fromRecordId($id, $dataConnector)
1448 $resourceLink->dataConnector = $dataConnector;
1449 $resourceLink->load($id);
1451 return $resourceLink;
1465 private function load($id =
null)
1481 private function checkValueType($ltiOutcome, $supportedTypes =
null)
1483 if (empty($supportedTypes)) {
1484 $supportedTypes = explode(
',',
1485 str_replace(
' ',
'', strtolower($this->
getSetting(
'ext_ims_lis_resultvalue_sourcedids', self::EXT_TYPE_DECIMAL))));
1487 $type = $ltiOutcome->type;
1488 $value = $ltiOutcome->getValue();
1490 $ok = in_array($type, $supportedTypes) || empty($value);
1493 if ($type === self::EXT_TYPE_PERCENTAGE) {
1494 if (substr($value, -1) ===
'%') {
1495 $value = substr($value, 0, -1);
1497 $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1499 $ltiOutcome->setValue($value / 100);
1502 } elseif ($type === self::EXT_TYPE_RATIO) {
1503 $parts = explode(
'/', $value, 2);
1504 $ok = (count($parts) === 2) && is_numeric($parts[0]) && is_numeric($parts[1]) && ($parts[0] >= 0) && ($parts[1] > 0);
1506 $ltiOutcome->setValue($parts[0] / $parts[1]);
1510 } elseif ($type === self::EXT_TYPE_LETTER_AF) {
1511 if (in_array(self::EXT_TYPE_LETTER_AF_PLUS, $supportedTypes)) {
1514 } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1519 } elseif ($type === self::EXT_TYPE_LETTER_AF_PLUS) {
1520 if (in_array(self::EXT_TYPE_LETTER_AF, $supportedTypes) && (strlen($value) === 1)) {
1523 } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1528 } elseif ($type === self::EXT_TYPE_TEXT) {
1529 $ok = is_numeric($value) && ($value >= 0) && ($value <= 1);
1532 } elseif (substr($value, -1) ===
'%') {
1533 $value = substr($value, 0, -1);
1534 $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1536 if (in_array(self::EXT_TYPE_PERCENTAGE, $supportedTypes)) {
1539 $ltiOutcome->setValue($value / 100);
1560 private function doService($type, $url, $params, $scope)
1563 $this->extRequest =
'';
1564 $this->extRequestHeaders =
'';
1565 $this->extResponse =
'';
1566 $this->extResponseHeaders =
'';
1567 $this->lastServiceRequest =
null;
1569 $params[
'lti_version'] = $this->
getPlatform()->ltiVersion;
1570 $params[
'lti_message_type'] = $type;
1574 $paramstosign = $params;
1576 $paramstosign =
null;
1577 $accessToken = $this->platform->getAccessToken();
1579 if (empty($accessToken)) {
1581 $this->platform->setAccessToken($accessToken);
1583 if (!$accessToken->hasScope($scope) && (empty(
Tool::$defaultTool) || !in_array($scope,
1585 $accessToken->expires = time();
1586 $accessToken->get($scope,
true);
1587 $this->platform->setAccessToken($accessToken);
1593 $signed = $this->platform->addSignature($url, $paramstosign,
'POST',
'application/x-www-form-urlencoded');
1595 if (is_array($signed)) {
1596 $http =
new HttpMessage($url,
'POST', $signed,
'Accept: application/xml');
1598 $http =
new HttpMessage($url,
'POST', $params,
"{$signed}\nAccept: application/xml");
1600 if ($http->send()) {
1602 $this->extResponse = $http->response;
1603 $this->extResponseHeaders = $http->responseHeaders;
1605 $this->extDoc =
new DOMDocument();
1606 $this->extDoc->loadXML($http->response);
1607 $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement);
1608 if (isset($this->extNodes[
'statusinfo'][
'codemajor']) && ($this->extNodes[
'statusinfo'][
'codemajor'] ===
'Success')) {
1611 }
catch (\Exception $e) {
1615 $retry = $retry && !$newToken && !$ok;
1617 $accessToken = $this->platform->getAccessToken();
1618 $accessToken->expires = time();
1619 $accessToken->get($scope,
true);
1620 $this->platform->setAccessToken($accessToken);
1624 $this->extRequest = $http->request;
1625 $this->extRequestHeaders = $http->requestHeaders;
1626 $this->lastServiceRequest = $http;
1640 private function doResultService($userResult, $url)
1643 $this->extRequest =
'';
1644 $this->extRequestHeaders =
'';
1645 $this->extResponse =
'';
1646 $this->extResponseHeaders =
'';
1647 $this->lastServiceRequest =
null;
1650 $outcome = $resultService->get($userResult);
1651 $http = $resultService->getHttpMessage();
1652 $this->extResponse = $http->response;
1653 $this->extResponseHeaders = $http->responseHeaders;
1654 $this->extRequest = $http->request;
1655 $this->extRequestHeaders = $http->requestHeaders;
1656 $this->lastServiceRequest = $http;
1671 private function doScoreService($ltiOutcome, $userResult, $url)
1674 $this->extRequest =
'';
1675 $this->extRequestHeaders =
'';
1676 $this->extResponse =
'';
1677 $this->extResponseHeaders =
'';
1678 $this->lastServiceRequest =
null;
1681 $scoreService->submit($ltiOutcome, $userResult);
1682 $http = $scoreService->getHttpMessage();
1683 $this->extResponse = $http->response;
1684 $this->extResponseHeaders = $http->responseHeaders;
1686 $this->extRequest = $http->request;
1687 $this->extRequestHeaders = $http->requestHeaders;
1688 $this->lastServiceRequest = $http;
1703 private function doLTI11Service($type, $url, $xml)
1706 $this->extRequest =
'';
1707 $this->extRequestHeaders =
'';
1708 $this->extResponse =
'';
1709 $this->extResponseHeaders =
'';
1710 $this->lastServiceRequest =
null;
1713 $xmlRequest = <<< EOD
1714<?xml version =
"1.0" encoding =
"UTF-8"?>
1715<imsx_POXEnvelopeRequest xmlns =
"http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
1717 <imsx_POXRequestHeaderInfo>
1718 <imsx_version>V1.0</imsx_version>
1719 <imsx_messageIdentifier>{$id}</imsx_messageIdentifier>
1720 </imsx_POXRequestHeaderInfo>
1727</imsx_POXEnvelopeRequest>
1729 $scope =
'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome';
1733 $accessToken = $this->platform->getAccessToken();
1735 if (empty($accessToken)) {
1737 $this->platform->setAccessToken($accessToken);
1739 if (!$accessToken->hasScope($scope) && (empty(
Tool::$defaultTool) || !in_array($scope,
1741 $accessToken->expires = time();
1742 $accessToken->get($scope,
true);
1743 $this->platform->setAccessToken($accessToken);
1749 $header = $this->
getPlatform()->addSignature($url, $xmlRequest,
'POST',
'application/xml');
1750 $header .=
"\nAccept: application/xml";
1752 $http =
new HttpMessage($url,
'POST', $xmlRequest, $header);
1753 if ($http->send()) {
1755 $this->extResponse = $http->response;
1756 $this->extResponseHeaders = $http->responseHeaders;
1758 $this->extDoc =
new DOMDocument();
1759 $this->extDoc->loadXML($http->response);
1760 $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement);
1761 if (isset($this->extNodes[
'imsx_POXHeader'][
'imsx_POXResponseHeaderInfo'][
'imsx_statusInfo'][
'imsx_codeMajor']) &&
1762 ($this->extNodes[
'imsx_POXHeader'][
'imsx_POXResponseHeaderInfo'][
'imsx_statusInfo'][
'imsx_codeMajor'] ===
'success')) {
1765 }
catch (\Exception $e) {
1769 $retry = $retry && !$newToken && !$ok;
1771 $accessToken = $this->platform->getAccessToken();
1772 $accessToken->expires = time();
1773 $accessToken->get($scope,
true);
1774 $this->platform->setAccessToken($accessToken);
1778 $this->extRequest = $http->request;
1779 $this->extRequestHeaders = $http->requestHeaders;
1780 $this->lastServiceRequest = $http;
1791 private function getLineItemService()
1793 $url = $this->
getSetting(
'custom_lineitems_url');
1797 $lineItemService =
false;
1800 return $lineItemService;
1810 private function domnodeToArray($node)
1813 switch ($node->nodeType) {
1814 case XML_CDATA_SECTION_NODE:
1816 $output = trim($node->textContent);
1818 case XML_ELEMENT_NODE:
1819 for ($i = 0; $i < $node->childNodes->length; $i++) {
1820 $child = $node->childNodes->item($i);
1821 $v = $this->domnodeToArray($child);
1822 if (isset($child->tagName)) {
1823 $name = explode(
':', $child->tagName, 2);
1824 $output[$name[count($name) - 1]][] = $v;
1832 if (is_array($output)) {
1833 if ($node->hasAttributes()) {
1834 foreach ($node->attributes as $attrNode) {
1835 $output[
'@attributes'][$attrNode->name] = (string) $attrNode->value;
1838 foreach ($output as $t => $v) {
1839 if (is_array($v) && (count($v) === 1) && ($t !==
'@attributes')) {
1840 $output[$t] = $v[0];
Class to represent an HTTP message.
Trait to handle API hook registrations.
static fromRecordId($id, $dataConnector)
Load the context from the database.
Class to provide a connection to a persistent store for LTI objects.
Class to represent an HTTP message request.
Class to represent a line-item.
Class to represent a platform resource link.
$groups
User groups (null if the platform does not support the groups enhancement)
hasMembershipsService()
Check if a Memberships service is available.
__construct()
Class constructor.
getId()
Get resource link ID.
const EXT_TYPE_DECIMAL
Decimal outcome type.
getMemberships($withGroups=false)
Get Memberships.
hasToolSettingsService()
Check if the Tool Settings service is available.
setContext($context)
Set context.
const EXT_READ
Read action.
$lastServiceRequest
HttpMessage object for last service request.
hasScoreService()
Check if the Score service is available.
static fromContext($context, $ltiResourceLinkId, $tempId=null)
Class constructor from context.
$updated
Timestamp for when the object was last updated.
const EXT_TYPE_PERCENTAGE
Percentage outcome type.
const EXT_UPDATE
Update action.
getMembership()
Get Membership.
setSetting($name, $value=null)
Set a setting value.
getDataConnector()
Get the data connector.
static fromConsumer($consumer, $ltiResourceLinkId, $tempId=null)
Class constructor from consumer.
getConsumer()
Get tool consumer.
const EXT_TYPE_TEXT
Free text outcome type.
$shareApproved
Whether the sharing request has been approved by the primary resource link.
$extResponseHeaders
Response header from last service request.
hasLineItemService()
Check if the Line-item service is available.
hasOutcomesService()
Check if an Outcomes service is available.
getToolSettings($mode=Service\ToolSettings::MODE_CURRENT_LEVEL, $simple=true)
Get Tool Settings.
doOutcomesService($action, $ltiOutcome, $userResult)
Perform an Outcomes service request.
getUserResultSourcedIDs($localOnly=false, $idScope=null)
Obtain an array of UserResult objects for users with a result sourcedId.
const EXT_TYPE_RATIO
Ratio outcome type.
$extRequest
Request for last service request.
setPlatformId($platformId)
Set platform ID.
hasMembershipService()
Check if the Membership service is supported.
setToolSettings($settings=array())
Set Tool Settings.
setConsumerId($consumerId)
Set tool consumer ID.
$groupSets
User group sets (null if the platform does not support the groups enhancement)
initialize()
Initialise the resource link.
hasAssessmentControlService()
Check if the Assessment Control service is available.
const EXT_TYPE_PASS_FAIL
Pass/fail outcome type.
saveSettings()
Save setting values.
$created
Timestamp for when the object was created.
hasResultService()
Check if the Result service is available.
doSettingService($action, $value=null)
Perform a Setting service request.
getPlatformId()
Get platform ID.
const EXT_TYPE_LETTER_AF_PLUS
Letter (A-F) with optional +/- outcome type.
getShares()
Get an array of ResourceLinkShare objects for each resource link which is sharing this context.
const EXT_WRITE
Write (create/update) action.
$ltiResourceLinkId
Resource link ID as supplied in the last connection request.
doMembershipsService($withGroups=false)
Perform a Memberships extension service request.
static fromPlatform($platform, $ltiResourceLinkId, $tempId=null)
Class constructor from platform.
setSettings($settings)
Set an array of all setting values.
getConsumerId()
Get tool consumer ID.
getOutcomes($limit=null)
Get all outcomes.
getPlatform()
Get platform.
const EXT_DELETE
Delete action.
$primaryResourceLinkId
Primary key value for resource link being shared (if any).
getRecordId()
Get resource link record ID.
getKey()
Get consumer key.
getSetting($name, $default='')
Get a setting value.
const EXT_TYPE_LETTER_AF
Letter (A-F) outcome type.
getLineItems($resourceId=null, $tag=null, $limit=null)
Get line-items.
setRecordId($id)
Set resource link record ID.
setContextId($contextId)
Set context ID.
initialise()
Initialise the resource link.
const EXT_CREATE
Create action.
$extRequestHeaders
Request headers for last service request.
getSettings()
Get an array of all setting values.
hasSettingService()
Check if the Setting extension service is available.
doAssessmentControlAction($assessmentControlAction, $user, $attemptNumber)
Perform an Assessment Control action.
getContextId()
Get context record ID.
$extResponse
Response from last service request.
save()
Save the resource link to the database.
createLineItem($lineItem)
Create a new line-item.
static fromRecordId($id, $dataConnector)
Load the resource link from the database.
Class to implement the Assessment Control service.
Class to implement the Membership service.
Class to implement the Result service.
static $SCOPE
Access scope.
Class to implement the Score service.
static $SCOPE
Access scope.
Class to implement a service.
static parseRoles($roles, $ltiVersion=Util::LTI_VERSION1, $addPrincipalRole=false)
Parse a set of roles to comply with a specified version of LTI.
static fromResourceLink($resourceLink, $ltiUserId)
Class constructor from resource link.
static logDebug($message, $showSource=false)
Log a debug message.