187 private $consumer =
null;
194 private $consumerId =
null;
201 private $context =
null;
208 private $contextId =
null;
215 private $settings =
null;
222 private $settingsChanged =
false;
229 private $extDoc =
null;
236 private $extNodes =
null;
243 private $dataConnector =
null;
259 $this->settings = array();
260 $this->groupSets =
null;
261 $this->groups =
null;
262 $this->primaryResourceLinkId =
null;
263 $this->shareApproved =
null;
264 $this->created =
null;
265 $this->updated =
null;
283 public function save()
287 $this->settingsChanged =
false;
298 public function delete()
310 if (is_null($this->consumer)) {
311 if (!is_null($this->context) || !is_null($this->contextId)) {
312 $this->consumer = $this->
getContext()->getConsumer();
318 return $this->consumer;
328 return $this->consumerId;
338 $this->consumer =
null;
339 $this->consumerId = $consumerId;
349 if (is_null($this->context) && !is_null($this->contextId)) {
353 return $this->context;
363 return $this->contextId;
373 $this->context =
null;
374 $this->contextId = $contextId;
392 public function getId()
424 return $this->dataConnector;
435 public function getSetting($name, $default =
'')
437 if (array_key_exists($name, $this->settings)) {
438 $value = $this->settings[$name];
452 public function setSetting($name, $value =
null)
455 if ($value !== $old_value) {
456 if (!empty($value)) {
457 $this->settings[$name] = $value;
459 unset($this->settings[$name]);
461 $this->settingsChanged =
true;
472 return $this->settings;
482 $this->settings = $settings;
492 if ($this->settingsChanged) {
508 $has = !empty($this->
getSetting(
'ext_ims_lis_basic_outcome_url')) || !empty($this->
getSetting(
'lis_outcome_service_url'));
510 $has = self::hasApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode());
523 if (!empty($this->contextId)) {
527 $has = !empty($this->
getSetting(
'ext_ims_lis_memberships_url'));
530 $has = self::hasApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode());
542 $url = $this->
getSetting(
'ext_ims_lti_tool_setting_url');
559 $this->extResponse =
'';
561 $sourceResourceLink = $userresult->getResourceLink();
562 $sourcedId = $userresult->ltiResultSourcedId;
565 $urlLTI11 = $sourceResourceLink->getSetting(
'lis_outcome_service_url');
566 $urlExt = $sourceResourceLink->getSetting(
'ext_ims_lis_basic_outcome_url');
567 if ($urlExt || $urlLTI11) {
570 if ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
574 $do =
'basic-lis-readresult';
578 if ($urlLTI11 && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
579 $do =
'replaceResult';
580 } elseif ($this->checkValueType($ltiOutcome)) {
582 $do =
'basic-lis-updateresult';
586 if ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
587 $do =
'deleteResult';
590 $do =
'basic-lis-deleteresult';
596 $value = $ltiOutcome->getValue();
597 if (is_null($value)) {
602 if ($action === self::EXT_WRITE) {
607 <language>{$ltiOutcome->language}</language>
608 <textString>{$value}</textString>
613 $sourcedId = htmlentities($sourcedId);
617 <sourcedId>{$sourcedId}</sourcedId>
621 if ($this->doLTI11Service($do, $urlLTI11, $xml)) {
624 if (!isset($this->extNodes[
'imsx_POXBody'][
"{$do}Response"][
'result'][
'resultScore'][
'textString'])) {
627 $ltiOutcome->setValue($this->extNodes[
'imsx_POXBody'][
"{$do}Response"][
'result'][
'resultScore'][
'textString']);
637 $params[
'sourcedid'] = $sourcedId;
638 $params[
'result_resultscore_textstring'] = $value;
639 if (!empty($ltiOutcome->language)) {
640 $params[
'result_resultscore_language'] = $ltiOutcome->language;
642 if (!empty($ltiOutcome->status)) {
643 $params[
'result_statusofresult'] = $ltiOutcome->status;
645 if (!empty($ltiOutcome->date)) {
646 $params[
'result_date'] = $ltiOutcome->date;
648 if (!empty($ltiOutcome->type)) {
649 $params[
'result_resultvaluesourcedid'] = $ltiOutcome->type;
651 if (!empty($ltiOutcome->data_source)) {
652 $params[
'result_datasource'] = $ltiOutcome->data_source;
654 if ($this->doService($do, $urlExt, $params)) {
657 if (isset($this->extNodes[
'result'][
'resultscore'][
'textstring'])) {
658 $response = $this->extNodes[
'result'][
'resultscore'][
'textstring'];
668 if (is_array($response) && (count($response) <= 0)) {
672 if (($response ===
false) && $this->hasApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode())) {
673 $className = $this->getApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode());
674 $hook =
new $className($this);
675 $response = $hook->doOutcomesService($action, $ltiOutcome, $userresult);
709 $this->extResponse =
'';
712 $do =
'basic-lti-loadsetting';
715 $do =
'basic-lti-savesetting';
718 $do =
'basic-lti-deletesetting';
723 $url = $this->
getSetting(
'ext_ims_lti_tool_setting_url');
725 $params[
'id'] = $this->
getSetting(
'ext_ims_lti_tool_setting_id');
726 if (is_null($value)) {
729 $params[
'setting'] = $value;
731 if ($this->doService($do, $url, $params)) {
734 if (isset($this->extNodes[
'setting'][
'value'])) {
735 $response = $this->extNodes[
'setting'][
'value'];
736 if (is_array($response)) {
742 $this->
setSetting(
'ext_ims_lti_tool_setting', $value);
763 $has = !empty($this->
getSetting(
'custom_link_setting_url'));
765 $has = self::hasApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode());
782 if (!empty($this->
getSetting(
'custom_link_setting_url'))) {
783 $url = $this->
getSetting(
'custom_link_setting_url');
785 $settings = $service->get($mode);
786 $this->lastServiceRequest = $service->getHTTPMessage();
787 $ok = $settings !==
false;
789 if (!$ok && $this->hasApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode())) {
790 $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode());
791 $hook =
new $className($this);
792 $settings = $hook->getToolSettings($mode, $simple);
808 if (!empty($this->
getSetting(
'custom_link_setting_url'))) {
809 $url = $this->
getSetting(
'custom_link_setting_url');
811 $ok = $service->set($settings);
812 $this->lastServiceRequest = $service->getHTTPMessage();
814 if (!$ok && $this->hasApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode())) {
815 $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode());
816 $hook =
new $className($this);
817 $ok = $hook->setToolSettings($settings);
859 $userResults = array();
860 $hasLtiservice = !empty($this->contextId) && !empty($this->
getContext()->
getSetting(
'custom_context_memberships_url'));
861 $hasExtService = !empty($this->
getSetting(
'ext_ims_lis_memberships_url'));
862 $hasApiHook = $this->hasApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode());
863 if ($hasLtiservice && (!$withGroups || (!$hasExtService && !$hasApiHook))) {
864 $url = $this->
getContext()->getSetting(
'custom_context_memberships_url');
866 $userResults = $service->get();
867 $this->lastServiceRequest = $service->getHTTPMessage();
868 $ok = $userResults !==
false;
870 if (!$ok && $hasExtService) {
871 $this->extResponse =
'';
872 $url = $this->
getSetting(
'ext_ims_lis_memberships_url');
874 $params[
'id'] = $this->
getSetting(
'ext_ims_lis_memberships_id');
876 $ok = $this->doService(
'basic-lis-readmembershipsforcontextwithgroups', $url, $params);
879 $ok = $this->doService(
'basic-lis-readmembershipsforcontext', $url, $params);
883 $this->groupSets = array();
884 $this->groups = array();
885 if (!isset($this->extNodes[
'memberships'][
'member'])) {
887 } elseif (!isset($this->extNodes[
'memberships'][
'member'][0])) {
889 $members[0] = $this->extNodes[
'memberships'][
'member'];
891 $members = $this->extNodes[
'memberships'][
'member'];
894 for ($i = 0; $i < count($members); $i++) {
899 $firstname = (isset($members[$i][
'person_name_given'])) ? $members[$i][
'person_name_given'] :
'';
900 $lastname = (isset($members[$i][
'person_name_family'])) ? $members[$i][
'person_name_family'] :
'';
901 $fullname = (isset($members[$i][
'person_name_full'])) ? $members[$i][
'person_name_full'] :
'';
902 $userresult->setNames($firstname, $lastname, $fullname);
905 $email = (isset($members[$i][
'person_contact_email_primary'])) ? $members[$i][
'person_contact_email_primary'] :
'';
906 $userresult->setEmail($email, $this->
getConsumer()->defaultEmail);
909 if (isset($members[$i][
'roles'])) {
914 if (!isset($members[$i][
'groups'][
'group'])) {
916 } elseif (!isset($members[$i][
'groups'][
'group'][0])) {
918 $groups[0] = $members[$i][
'groups'][
'group'];
920 $groups = $members[$i][
'groups'][
'group'];
922 for ($j = 0; $j < count(
$groups); $j++) {
924 if (isset($group[
'set'])) {
925 $set_id = $group[
'set'][
'id'];
926 if (!isset($this->groupSets[$set_id])) {
927 $this->groupSets[$set_id] = array(
'title' => $group[
'set'][
'title'],
'groups' => array(),
928 'num_members' => 0,
'num_staff' => 0,
'num_learners' => 0);
930 $this->groupSets[$set_id][
'num_members'] ++;
931 if ($userresult->isStaff()) {
932 $this->groupSets[$set_id][
'num_staff'] ++;
934 if ($userresult->isLearner()) {
935 $this->groupSets[$set_id][
'num_learners'] ++;
937 if (!in_array($group[
'id'], $this->groupSets[$set_id][
'groups'])) {
938 $this->groupSets[$set_id][
'groups'][] = $group[
'id'];
940 $this->groups[$group[
'id']] = array(
'title' => $group[
'title'],
'set' => $set_id);
942 $this->groups[$group[
'id']] = array(
'title' => $group[
'title']);
944 $userresult->groups[] = $group[
'id'];
948 if (isset($members[$i][
'lis_result_sourcedid'])) {
949 $userresult->ltiResultSourcedId = $members[$i][
'lis_result_sourcedid'];
952 $userResults[] = $userresult;
959 foreach ($oldUsers as $id => $userresult) {
960 $userresult->delete();
963 $userResults =
false;
965 $ok = $userResults !==
false;
967 if (!$ok && $hasApiHook) {
968 $className = $this->getApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->
getConsumer()->getFamilyCode());
969 $hook =
new $className($this);
970 $userResults = $hook->getMemberships($withGroups);
989 return $this->
getDataConnector()->getUserResultSourcedIDsResourceLink($this, $localOnly, $idScope);
1014 $resourceLink->consumer = $consumer;
1015 $resourceLink->dataConnector = $consumer->getDataConnector();
1018 $resourceLink->load();
1019 if (is_null($resourceLink->id) && !empty($tempId)) {
1020 $resourceLink->ltiResourceLinkId = $tempId;
1021 $resourceLink->load();
1026 return $resourceLink;
1041 $resourceLink->setContextId($context->getRecordId());
1042 $resourceLink->context = $context;
1043 $resourceLink->dataConnector = $context->getDataConnector();
1046 $resourceLink->load();
1047 if (is_null($resourceLink->id) && !empty($tempId)) {
1048 $resourceLink->ltiResourceLinkId = $tempId;
1049 $resourceLink->load();
1054 return $resourceLink;
1065 public static function fromRecordId($id, $dataConnector)
1068 $resourceLink->dataConnector = $dataConnector;
1069 $resourceLink->load($id);
1071 return $resourceLink;
1085 private function load($id =
null)
1101 private function checkValueType($ltiOutcome, $supportedTypes =
null)
1103 if (empty($supportedTypes)) {
1104 $supportedTypes = explode(
',',
1105 str_replace(
' ',
'', strtolower($this->
getSetting(
'ext_ims_lis_resultvalue_sourcedids', self::EXT_TYPE_DECIMAL))));
1107 $type = $ltiOutcome->type;
1108 $value = $ltiOutcome->getValue();
1110 $ok = in_array($type, $supportedTypes) || (strlen($value) <= 0);
1113 if ($type === self::EXT_TYPE_PERCENTAGE) {
1114 if (substr($value, -1) ===
'%') {
1115 $value = substr($value, 0, -1);
1117 $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1119 $ltiOutcome->setValue($value / 100);
1122 } elseif ($type === self::EXT_TYPE_RATIO) {
1123 $parts = explode(
'/', $value, 2);
1124 $ok = (count($parts) === 2) && is_numeric($parts[0]) && is_numeric($parts[1]) && ($parts[0] >= 0) && ($parts[1] > 0);
1126 $ltiOutcome->setValue($parts[0] / $parts[1]);
1130 } elseif ($type === self::EXT_TYPE_LETTER_AF) {
1131 if (in_array(self::EXT_TYPE_LETTER_AF_PLUS, $supportedTypes)) {
1134 } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1139 } elseif ($type === self::EXT_TYPE_LETTER_AF_PLUS) {
1140 if (in_array(self::EXT_TYPE_LETTER_AF, $supportedTypes) && (strlen($value) === 1)) {
1143 } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1148 } elseif ($type === self::EXT_TYPE_TEXT) {
1149 $ok = is_numeric($value) && ($value >= 0) && ($value <= 1);
1152 } elseif (substr($value, -1) ===
'%') {
1153 $value = substr($value, 0, -1);
1154 $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1156 if (in_array(self::EXT_TYPE_PERCENTAGE, $supportedTypes)) {
1159 $ltiOutcome->setValue($value / 100);
1179 private function doService($type, $url, $params)
1182 $this->extRequest =
'';
1183 $this->extRequestHeaders =
'';
1184 $this->extResponse =
'';
1185 $this->extResponseHeaders =
'';
1186 $this->lastServiceRequest =
null;
1192 if ($http->send()) {
1193 $this->extResponse = $http->response;
1194 $this->extResponseHeaders = $http->responseHeaders;
1196 $this->extDoc =
new DOMDocument();
1197 $this->extDoc->loadXML($http->response);
1198 $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement);
1199 if (isset($this->extNodes[
'statusinfo'][
'codemajor']) && ($this->extNodes[
'statusinfo'][
'codemajor'] ===
'Success')) {
1202 }
catch (\Exception $e) {
1206 $this->extRequest = $http->request;
1207 $this->extRequestHeaders = $http->requestHeaders;
1208 $this->lastServiceRequest = $http;
1223 private function doLTI11Service($type, $url, $xml)
1226 $this->extRequest =
'';
1227 $this->extRequestHeaders =
'';
1228 $this->extResponse =
'';
1229 $this->extResponseHeaders =
'';
1230 $this->lastServiceRequest =
null;
1233 $xmlRequest = <<< EOD
1234 <?xml version =
"1.0" encoding =
"UTF-8"?>
1235 <imsx_POXEnvelopeRequest xmlns =
"http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
1237 <imsx_POXRequestHeaderInfo>
1238 <imsx_version>V1.0</imsx_version>
1239 <imsx_messageIdentifier>{$id}</imsx_messageIdentifier>
1240 </imsx_POXRequestHeaderInfo>
1247 </imsx_POXEnvelopeRequest>
1251 $header = $consumer->addSignature($url, $xmlRequest,
'POST',
'application/xml');
1253 $http =
new HTTPMessage($url,
'POST', $xmlRequest, $header);
1255 if ($http->send()) {
1256 $this->extResponse = $http->response;
1257 $this->extResponseHeaders = $http->responseHeaders;
1259 $this->extDoc =
new DOMDocument();
1260 $this->extDoc->loadXML($http->response);
1261 $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement);
1262 if (isset($this->extNodes[
'imsx_POXHeader'][
'imsx_POXResponseHeaderInfo'][
'imsx_statusInfo'][
'imsx_codeMajor']) &&
1263 ($this->extNodes[
'imsx_POXHeader'][
'imsx_POXResponseHeaderInfo'][
'imsx_statusInfo'][
'imsx_codeMajor'] ===
'success')) {
1266 }
catch (\Exception $e) {
1270 $this->extRequest = $http->request;
1271 $this->extRequestHeaders = $http->requestHeaders;
1272 $this->lastServiceRequest = $http;
1285 private function domnodeToArray($node)
1288 switch ($node->nodeType) {
1289 case XML_CDATA_SECTION_NODE:
1291 $output = trim($node->textContent);
1293 case XML_ELEMENT_NODE:
1294 for ($i = 0; $i < $node->childNodes->length; $i++) {
1295 $child = $node->childNodes->item($i);
1296 $v = $this->domnodeToArray($child);
1297 if (isset($child->tagName)) {
1298 $output[$child->tagName][] = $v;
1301 if (strlen($s) > 0) {
1306 if (is_array($output)) {
1307 if ($node->hasAttributes()) {
1308 foreach ($node->attributes as $attrNode) {
1310 $output[
'@attributes'][$attrNode->name] = (string) $attrNode->value;
1313 foreach ($output as $t => $v) {
1314 if (is_array($v) && count($v) == 1 && $t !=
'@attributes') {
1315 $output[$t] = $v[0];
const EXT_UPDATE
Update action.
getContextId()
Get context record ID.
static fromResourceLink($resourceLink, $ltiUserId)
Class constructor from resource link.
Class to provide a connection to a persistent store for LTI objects.
const EXT_TYPE_RATIO
Ratio outcome type.
Class to implement a service.
const EXT_DELETE
Delete action.
hasToolSettingsService()
Check if the Tool Settings service is available.
initialize()
Initialise the resource link.
initialise()
Initialise the resource link.
const EXT_WRITE
Write (create/update) action.
getSettings()
Get an array of all setting values.
const EXT_TYPE_PERCENTAGE
Percentage outcome type.
hasSettingService()
Check if the Setting extension service is available.
__construct()
Class constructor.
const EXT_TYPE_LETTER_AF
Letter (A-F) outcome type.
static fromRecordId($id, $dataConnector)
Load the context from the database.
setToolSettings($settings=array())
Perform a Tool Settings service request.
getKey()
Get tool consumer key.
$updated
Timestamp for when the object was last updated.
$groups
UserResult groups (null if the consumer does not support the groups enhancement)
getDataConnector()
Get the data connector.
getId()
Get resource link ID.
$ltiResourceLinkId
Resource link ID as supplied in the last connection request.
getMembership()
Get Membership.
$lastServiceRequest
HTTPMessage object for last service request.
const EXT_CREATE
Create action.
setRecordId($id)
Set resource link record ID.
getSetting($name, $default='')
Get a setting value.
static fromConsumer($consumer, $ltiResourceLinkId, $tempId=null)
Class constructor from consumer.
hasOutcomesService()
Check if an Outcomes service is available.
save()
Save the resource link to the database.
hasMembershipService()
Check if the Membership service is supported.
const EXT_TYPE_LETTER_AF_PLUS
Letter (A-F) with optional +/- outcome type.
Class to implement the Membership service.
getToolSettings($mode=Service\ToolSettings::MODE_CURRENT_LEVEL, $simple=true)
Get Tool Settings.
doSettingService($action, $value=null)
Perform a Setting service request.
const EXT_TYPE_TEXT
Free text outcome type.
setSetting($name, $value=null)
Set a setting value.
Class to represent an HTTP message request.
$extResponseHeaders
Response header from last service request.
$extRequestHeaders
Request headers for last service request.
getRecordId()
Get resource link record ID.
getConsumerId()
Get tool consumer ID.
doMembershipsService($withGroups=false)
Perform a Memberships extension service request.
static fromContext($context, $ltiResourceLinkId, $tempId=null)
Class constructor from context.
$primaryResourceLinkId
Consumer key value for resource link being shared (if any).
$extRequest
Request for last service request.
$extResponse
Response from last service request.
const EXT_READ
Read action.
$groupSets
UserResult group sets (null if the consumer does not support the groups enhancement)
saveSettings()
Save setting values.
static fromRecordId($id, $dataConnector)
Load the resource link from the database.
hasMembershipsService()
Check if a Memberships service is available.
$created
Timestamp for when the object was created.
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.
Trait to handle API hook registrations.
setConsumerId($consumerId)
Set tool consumer ID.
Class to represent a tool consumer resource link.
setSettings($settings)
Set an array of all setting values.
getMemberships($withGroups=false)
Get Memberships.
const EXT_TYPE_PASS_FAIL
Pass/fail outcome type.
$shareApproved
Whether the sharing request has been approved by the primary resource link.
setContextId($contextId)
Set context ID.
getConsumer()
Get tool consumer.
getShares()
Get an array of ResourceLinkShare objects for each resource link which is sharing this context.
const EXT_TYPE_DECIMAL
Decimal outcome type.