LTI Integration Library  3.1.0
PHP class library for building LTI integrations
ResourceLink.php
Go to the documentation of this file.
1 <?php
2 
3 namespace ceLTIc\LTI;
4 
9 use DOMDocument;
10 use DOMElement;
11 
21 {
22  use ApiHook;
23 
27  const EXT_READ = 1;
28 
32  const EXT_WRITE = 2;
33 
37  const EXT_DELETE = 3;
38 
42  const EXT_CREATE = 4;
43 
47  const EXT_UPDATE = 5;
48 
52  const EXT_TYPE_DECIMAL = 'decimal';
53 
57  const EXT_TYPE_PERCENTAGE = 'percentage';
58 
62  const EXT_TYPE_RATIO = 'ratio';
63 
67  const EXT_TYPE_LETTER_AF = 'letteraf';
68 
72  const EXT_TYPE_LETTER_AF_PLUS = 'letterafplus';
73 
77  const EXT_TYPE_PASS_FAIL = 'passfail';
78 
82  const EXT_TYPE_TEXT = 'freetext';
83 
89  public $title = null;
90 
96  public $ltiResourceLinkId = null;
97 
103  public $groupSets = null;
104 
110  public $groups = null;
111 
117  public $lastServiceRequest = null;
118 
124  public $extRequest = null;
125 
131  public $extRequestHeaders = null;
132 
138  public $extResponse = null;
139 
145  public $extResponseHeaders = null;
146 
152  public $primaryResourceLinkId = null;
153 
159  public $shareApproved = null;
160 
166  public $created = null;
167 
173  public $updated = null;
174 
180  private $id = null;
181 
187  private $consumer = null;
188 
194  private $consumerId = null;
195 
201  private $context = null;
202 
208  private $contextId = null;
209 
215  private $settings = null;
216 
222  private $settingsChanged = false;
223 
229  private $extDoc = null;
230 
236  private $extNodes = null;
237 
243  private $dataConnector = null;
244 
248  public function __construct()
249  {
250  $this->initialize();
251  }
252 
256  public function initialize()
257  {
258  $this->title = '';
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;
266  }
267 
273  public function initialise()
274  {
275  $this->initialize();
276  }
277 
283  public function save()
284  {
285  $ok = $this->getDataConnector()->saveResourceLink($this);
286  if ($ok) {
287  $this->settingsChanged = false;
288  }
289 
290  return $ok;
291  }
292 
298  public function delete()
299  {
300  return $this->getDataConnector()->deleteResourceLink($this);
301  }
302 
308  public function getConsumer()
309  {
310  if (is_null($this->consumer)) {
311  if (!is_null($this->context) || !is_null($this->contextId)) {
312  $this->consumer = $this->getContext()->getConsumer();
313  } else {
314  $this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector());
315  }
316  }
317 
318  return $this->consumer;
319  }
320 
326  public function getConsumerId()
327  {
328  return $this->consumerId;
329  }
330 
336  public function setConsumerId($consumerId)
337  {
338  $this->consumer = null;
339  $this->consumerId = $consumerId;
340  }
341 
347  public function getContext()
348  {
349  if (is_null($this->context) && !is_null($this->contextId)) {
350  $this->context = Context::fromRecordId($this->contextId, $this->getDataConnector());
351  }
352 
353  return $this->context;
354  }
355 
361  public function getContextId()
362  {
363  return $this->contextId;
364  }
365 
371  public function setContextId($contextId)
372  {
373  $this->context = null;
374  $this->contextId = $contextId;
375  }
376 
382  public function getKey()
383  {
384  return $this->getConsumer()->getKey();
385  }
386 
392  public function getId()
393  {
395  }
396 
402  public function getRecordId()
403  {
404  return $this->id;
405  }
406 
412  public function setRecordId($id)
413  {
414  $this->id = $id;
415  }
416 
422  public function getDataConnector()
423  {
424  return $this->dataConnector;
425  }
426 
435  public function getSetting($name, $default = '')
436  {
437  if (array_key_exists($name, $this->settings)) {
438  $value = $this->settings[$name];
439  } else {
440  $value = $default;
441  }
442 
443  return $value;
444  }
445 
452  public function setSetting($name, $value = null)
453  {
454  $old_value = $this->getSetting($name);
455  if ($value !== $old_value) {
456  if (!empty($value)) {
457  $this->settings[$name] = $value;
458  } else {
459  unset($this->settings[$name]);
460  }
461  $this->settingsChanged = true;
462  }
463  }
464 
470  public function getSettings()
471  {
472  return $this->settings;
473  }
474 
480  public function setSettings($settings)
481  {
482  $this->settings = $settings;
483  }
484 
490  public function saveSettings()
491  {
492  if ($this->settingsChanged) {
493  $ok = $this->save();
494  } else {
495  $ok = true;
496  }
497 
498  return $ok;
499  }
500 
506  public function hasOutcomesService()
507  {
508  $has = !empty($this->getSetting('ext_ims_lis_basic_outcome_url')) || !empty($this->getSetting('lis_outcome_service_url'));
509  if (!$has) {
510  $has = self::hasApiHook(self::$OUTCOMES_SERVICE_HOOK, $this->getConsumer()->getFamilyCode());
511  }
512  return $has;
513  }
514 
520  public function hasMembershipsService()
521  {
522  $has = false;
523  if (!empty($this->contextId)) {
524  $has = !empty($this->getContext()->getSetting('custom_context_memberships_url'));
525  }
526  if (!$has) {
527  $has = !empty($this->getSetting('ext_ims_lis_memberships_url'));
528  }
529  if (!$has) {
530  $has = self::hasApiHook(self::$MEMBERSHIPS_SERVICE_HOOK, $this->getConsumer()->getFamilyCode());
531  }
532  return $has;
533  }
534 
540  public function hasSettingService()
541  {
542  $url = $this->getSetting('ext_ims_lti_tool_setting_url');
543 
544  return !empty($url);
545  }
546 
556  public function doOutcomesService($action, $ltiOutcome, $userresult)
557  {
558  $response = false;
559  $this->extResponse = '';
560 // Lookup service details from the source resource link appropriate to the user (in case the destination is being shared)
561  $sourceResourceLink = $userresult->getResourceLink();
562  $sourcedId = $userresult->ltiResultSourcedId;
563 
564 // Use LTI 1.1 service in preference to extension service if it is available
565  $urlLTI11 = $sourceResourceLink->getSetting('lis_outcome_service_url');
566  $urlExt = $sourceResourceLink->getSetting('ext_ims_lis_basic_outcome_url');
567  if ($urlExt || $urlLTI11) {
568  switch ($action) {
569  case self::EXT_READ:
570  if ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
571  $do = 'readResult';
572  } elseif ($urlExt) {
573  $urlLTI11 = null;
574  $do = 'basic-lis-readresult';
575  }
576  break;
577  case self::EXT_WRITE:
578  if ($urlLTI11 && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) {
579  $do = 'replaceResult';
580  } elseif ($this->checkValueType($ltiOutcome)) {
581  $urlLTI11 = null;
582  $do = 'basic-lis-updateresult';
583  }
584  break;
585  case self::EXT_DELETE:
586  if ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) {
587  $do = 'deleteResult';
588  } elseif ($urlExt) {
589  $urlLTI11 = null;
590  $do = 'basic-lis-deleteresult';
591  }
592  break;
593  }
594  }
595  if (isset($do)) {
596  $value = $ltiOutcome->getValue();
597  if (is_null($value)) {
598  $value = '';
599  }
600  if ($urlLTI11) {
601  $xml = '';
602  if ($action === self::EXT_WRITE) {
603  $xml = <<<EOF
604 
605  <result>
606  <resultScore>
607  <language>{$ltiOutcome->language}</language>
608  <textString>{$value}</textString>
609  </resultScore>
610  </result>
611 EOF;
612  }
613  $sourcedId = htmlentities($sourcedId);
614  $xml = <<<EOF
615  <resultRecord>
616  <sourcedGUID>
617  <sourcedId>{$sourcedId}</sourcedId>
618  </sourcedGUID>{$xml}
619  </resultRecord>
620 EOF;
621  if ($this->doLTI11Service($do, $urlLTI11, $xml)) {
622  switch ($action) {
623  case self::EXT_READ:
624  if (!isset($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString'])) {
625  break;
626  } else {
627  $ltiOutcome->setValue($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString']);
628  }
629  case self::EXT_WRITE:
630  case self::EXT_DELETE:
631  $ok = true;
632  break;
633  }
634  }
635  } else {
636  $params = array();
637  $params['sourcedid'] = $sourcedId;
638  $params['result_resultscore_textstring'] = $value;
639  if (!empty($ltiOutcome->language)) {
640  $params['result_resultscore_language'] = $ltiOutcome->language;
641  }
642  if (!empty($ltiOutcome->status)) {
643  $params['result_statusofresult'] = $ltiOutcome->status;
644  }
645  if (!empty($ltiOutcome->date)) {
646  $params['result_date'] = $ltiOutcome->date;
647  }
648  if (!empty($ltiOutcome->type)) {
649  $params['result_resultvaluesourcedid'] = $ltiOutcome->type;
650  }
651  if (!empty($ltiOutcome->data_source)) {
652  $params['result_datasource'] = $ltiOutcome->data_source;
653  }
654  if ($this->doService($do, $urlExt, $params)) {
655  switch ($action) {
656  case self::EXT_READ:
657  if (isset($this->extNodes['result']['resultscore']['textstring'])) {
658  $response = $this->extNodes['result']['resultscore']['textstring'];
659  }
660  break;
661  case self::EXT_WRITE:
662  case self::EXT_DELETE:
663  $response = true;
664  break;
665  }
666  }
667  }
668  if (is_array($response) && (count($response) <= 0)) {
669  $response = '';
670  }
671  }
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);
676  }
677 
678  return $response;
679  }
680 
693  public function doMembershipsService($withGroups = false)
694  {
695  return $this->getMemberships($withGroups);
696  }
697 
706  public function doSettingService($action, $value = null)
707  {
708  $response = false;
709  $this->extResponse = '';
710  switch ($action) {
711  case self::EXT_READ:
712  $do = 'basic-lti-loadsetting';
713  break;
714  case self::EXT_WRITE:
715  $do = 'basic-lti-savesetting';
716  break;
717  case self::EXT_DELETE:
718  $do = 'basic-lti-deletesetting';
719  break;
720  }
721  if (isset($do)) {
722 
723  $url = $this->getSetting('ext_ims_lti_tool_setting_url');
724  $params = array();
725  $params['id'] = $this->getSetting('ext_ims_lti_tool_setting_id');
726  if (is_null($value)) {
727  $value = '';
728  }
729  $params['setting'] = $value;
730 
731  if ($this->doService($do, $url, $params)) {
732  switch ($action) {
733  case self::EXT_READ:
734  if (isset($this->extNodes['setting']['value'])) {
735  $response = $this->extNodes['setting']['value'];
736  if (is_array($response)) {
737  $response = '';
738  }
739  }
740  break;
741  case self::EXT_WRITE:
742  $this->setSetting('ext_ims_lti_tool_setting', $value);
743  $this->saveSettings();
744  $response = true;
745  break;
746  case self::EXT_DELETE:
747  $response = true;
748  break;
749  }
750  }
751  }
752 
753  return $response;
754  }
755 
761  public function hasToolSettingsService()
762  {
763  $has = !empty($this->getSetting('custom_link_setting_url'));
764  if (!$has) {
765  $has = self::hasApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getConsumer()->getFamilyCode());
766  }
767  return $has;
768  }
769 
778  public function getToolSettings($mode = Service\ToolSettings::MODE_CURRENT_LEVEL, $simple = true)
779  {
780  $ok = false;
781  $settings = array();
782  if (!empty($this->getSetting('custom_link_setting_url'))) {
783  $url = $this->getSetting('custom_link_setting_url');
784  $service = new Service\ToolSettings($this, $url, $simple);
785  $settings = $service->get($mode);
786  $this->lastServiceRequest = $service->getHTTPMessage();
787  $ok = $settings !== false;
788  }
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);
793  }
794 
795  return $settings;
796  }
797 
805  public function setToolSettings($settings = array())
806  {
807  $ok = false;
808  if (!empty($this->getSetting('custom_link_setting_url'))) {
809  $url = $this->getSetting('custom_link_setting_url');
810  $service = new Service\ToolSettings($this, $url);
811  $ok = $service->set($settings);
812  $this->lastServiceRequest = $service->getHTTPMessage();
813  }
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);
818  }
819 
820  return $ok;
821  }
822 
831  public function hasMembershipService()
832  {
833  return $this->hasMembershipsService();
834  }
835 
844  public function getMembership()
845  {
846  return $this->getMemberships();
847  }
848 
856  public function getMemberships($withGroups = false)
857  {
858  $ok = false;
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');
865  $service = new Service\Membership($this, $url);
866  $userResults = $service->get();
867  $this->lastServiceRequest = $service->getHTTPMessage();
868  $ok = $userResults !== false;
869  }
870  if (!$ok && $hasExtService) {
871  $this->extResponse = '';
872  $url = $this->getSetting('ext_ims_lis_memberships_url');
873  $params = array();
874  $params['id'] = $this->getSetting('ext_ims_lis_memberships_id');
875  if ($withGroups) {
876  $ok = $this->doService('basic-lis-readmembershipsforcontextwithgroups', $url, $params);
877  }
878  if (!$ok) {
879  $ok = $this->doService('basic-lis-readmembershipsforcontext', $url, $params);
880  }
881  if ($ok) {
883  $this->groupSets = array();
884  $this->groups = array();
885  if (!isset($this->extNodes['memberships']['member'])) {
886  $members = array();
887  } elseif (!isset($this->extNodes['memberships']['member'][0])) {
888  $members = array();
889  $members[0] = $this->extNodes['memberships']['member'];
890  } else {
891  $members = $this->extNodes['memberships']['member'];
892  }
893 
894  for ($i = 0; $i < count($members); $i++) {
895 
896  $userresult = UserResult::fromResourceLink($this, $members[$i]['user_id']);
897 
898 // Set the user name
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);
903 
904 // Set the user email
905  $email = (isset($members[$i]['person_contact_email_primary'])) ? $members[$i]['person_contact_email_primary'] : '';
906  $userresult->setEmail($email, $this->getConsumer()->defaultEmail);
907 
908 // Set the user roles
909  if (isset($members[$i]['roles'])) {
910  $userresult->roles = ToolProvider::parseRoles($members[$i]['roles']);
911  }
912 
913 // Set the user groups
914  if (!isset($members[$i]['groups']['group'])) {
915  $groups = array();
916  } elseif (!isset($members[$i]['groups']['group'][0])) {
917  $groups = array();
918  $groups[0] = $members[$i]['groups']['group'];
919  } else {
920  $groups = $members[$i]['groups']['group'];
921  }
922  for ($j = 0; $j < count($groups); $j++) {
923  $group = $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);
929  }
930  $this->groupSets[$set_id]['num_members'] ++;
931  if ($userresult->isStaff()) {
932  $this->groupSets[$set_id]['num_staff'] ++;
933  }
934  if ($userresult->isLearner()) {
935  $this->groupSets[$set_id]['num_learners'] ++;
936  }
937  if (!in_array($group['id'], $this->groupSets[$set_id]['groups'])) {
938  $this->groupSets[$set_id]['groups'][] = $group['id'];
939  }
940  $this->groups[$group['id']] = array('title' => $group['title'], 'set' => $set_id);
941  } else {
942  $this->groups[$group['id']] = array('title' => $group['title']);
943  }
944  $userresult->groups[] = $group['id'];
945  }
946 
947 // If a result sourcedid is provided save the user
948  if (isset($members[$i]['lis_result_sourcedid'])) {
949  $userresult->ltiResultSourcedId = $members[$i]['lis_result_sourcedid'];
950  $userresult->save();
951  }
952  $userResults[] = $userresult;
953 
954 // Remove old user (if it exists)
955  unset($oldUsers[$userresult->getId(ToolProvider::ID_SCOPE_RESOURCE)]);
956  }
957 
958 // Delete any old users which were not in the latest list from the tool consumer
959  foreach ($oldUsers as $id => $userresult) {
960  $userresult->delete();
961  }
962  } else {
963  $userResults = false;
964  }
965  $ok = $userResults !== false;
966  }
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);
971  }
972 
973  return $userResults;
974  }
975 
987  public function getUserResultSourcedIDs($localOnly = false, $idScope = null)
988  {
989  return $this->getDataConnector()->getUserResultSourcedIDsResourceLink($this, $localOnly, $idScope);
990  }
991 
997  public function getShares()
998  {
999  return $this->getDataConnector()->getSharesResourceLink($this);
1000  }
1001 
1011  public static function fromConsumer($consumer, $ltiResourceLinkId, $tempId = null)
1012  {
1013  $resourceLink = new ResourceLink();
1014  $resourceLink->consumer = $consumer;
1015  $resourceLink->dataConnector = $consumer->getDataConnector();
1016  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1017  if (!empty($ltiResourceLinkId)) {
1018  $resourceLink->load();
1019  if (is_null($resourceLink->id) && !empty($tempId)) {
1020  $resourceLink->ltiResourceLinkId = $tempId;
1021  $resourceLink->load();
1022  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1023  }
1024  }
1025 
1026  return $resourceLink;
1027  }
1028 
1038  public static function fromContext($context, $ltiResourceLinkId, $tempId = null)
1039  {
1040  $resourceLink = new ResourceLink();
1041  $resourceLink->setContextId($context->getRecordId());
1042  $resourceLink->context = $context;
1043  $resourceLink->dataConnector = $context->getDataConnector();
1044  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1045  if (!empty($ltiResourceLinkId)) {
1046  $resourceLink->load();
1047  if (is_null($resourceLink->id) && !empty($tempId)) {
1048  $resourceLink->ltiResourceLinkId = $tempId;
1049  $resourceLink->load();
1050  $resourceLink->ltiResourceLinkId = $ltiResourceLinkId;
1051  }
1052  }
1053 
1054  return $resourceLink;
1055  }
1056 
1065  public static function fromRecordId($id, $dataConnector)
1066  {
1067  $resourceLink = new ResourceLink();
1068  $resourceLink->dataConnector = $dataConnector;
1069  $resourceLink->load($id);
1070 
1071  return $resourceLink;
1072  }
1073 
1074 ###
1075 ### PRIVATE METHODS
1076 ###
1077 
1085  private function load($id = null)
1086  {
1087  $this->initialize();
1088  $this->id = $id;
1089 
1090  return $this->getDataConnector()->loadResourceLink($this);
1091  }
1092 
1101  private function checkValueType($ltiOutcome, $supportedTypes = null)
1102  {
1103  if (empty($supportedTypes)) {
1104  $supportedTypes = explode(',',
1105  str_replace(' ', '', strtolower($this->getSetting('ext_ims_lis_resultvalue_sourcedids', self::EXT_TYPE_DECIMAL))));
1106  }
1107  $type = $ltiOutcome->type;
1108  $value = $ltiOutcome->getValue();
1109 // Check whether the type is supported or there is no value
1110  $ok = in_array($type, $supportedTypes) || (strlen($value) <= 0);
1111  if (!$ok) {
1112 // Convert numeric values to decimal
1113  if ($type === self::EXT_TYPE_PERCENTAGE) {
1114  if (substr($value, -1) === '%') {
1115  $value = substr($value, 0, -1);
1116  }
1117  $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1118  if ($ok) {
1119  $ltiOutcome->setValue($value / 100);
1120  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1121  }
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);
1125  if ($ok) {
1126  $ltiOutcome->setValue($parts[0] / $parts[1]);
1127  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1128  }
1129 // Convert letter_af to letter_af_plus or text
1130  } elseif ($type === self::EXT_TYPE_LETTER_AF) {
1131  if (in_array(self::EXT_TYPE_LETTER_AF_PLUS, $supportedTypes)) {
1132  $ok = true;
1133  $ltiOutcome->type = self::EXT_TYPE_LETTER_AF_PLUS;
1134  } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1135  $ok = true;
1136  $ltiOutcome->type = self::EXT_TYPE_TEXT;
1137  }
1138 // Convert letter_af_plus to letter_af or text
1139  } elseif ($type === self::EXT_TYPE_LETTER_AF_PLUS) {
1140  if (in_array(self::EXT_TYPE_LETTER_AF, $supportedTypes) && (strlen($value) === 1)) {
1141  $ok = true;
1142  $ltiOutcome->type = self::EXT_TYPE_LETTER_AF;
1143  } elseif (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) {
1144  $ok = true;
1145  $ltiOutcome->type = self::EXT_TYPE_TEXT;
1146  }
1147 // Convert text to decimal
1148  } elseif ($type === self::EXT_TYPE_TEXT) {
1149  $ok = is_numeric($value) && ($value >= 0) && ($value <= 1);
1150  if ($ok) {
1151  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1152  } elseif (substr($value, -1) === '%') {
1153  $value = substr($value, 0, -1);
1154  $ok = is_numeric($value) && ($value >= 0) && ($value <= 100);
1155  if ($ok) {
1156  if (in_array(self::EXT_TYPE_PERCENTAGE, $supportedTypes)) {
1157  $ltiOutcome->type = self::EXT_TYPE_PERCENTAGE;
1158  } else {
1159  $ltiOutcome->setValue($value / 100);
1160  $ltiOutcome->type = self::EXT_TYPE_DECIMAL;
1161  }
1162  }
1163  }
1164  }
1165  }
1166 
1167  return $ok;
1168  }
1169 
1179  private function doService($type, $url, $params)
1180  {
1181  $ok = false;
1182  $this->extRequest = '';
1183  $this->extRequestHeaders = '';
1184  $this->extResponse = '';
1185  $this->extResponseHeaders = '';
1186  $this->lastServiceRequest = null;
1187  if (!empty($url)) {
1188  $params = $this->getConsumer()->signParameters($url, $type, $this->getConsumer()->ltiVersion, $params);
1189 // Connect to tool consumer
1190  $http = new HTTPMessage($url, 'POST', $params);
1191 // Parse XML response
1192  if ($http->send()) {
1193  $this->extResponse = $http->response;
1194  $this->extResponseHeaders = $http->responseHeaders;
1195  try {
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')) {
1200  $ok = true;
1201  }
1202  } catch (\Exception $e) {
1203 
1204  }
1205  }
1206  $this->extRequest = $http->request;
1207  $this->extRequestHeaders = $http->requestHeaders;
1208  $this->lastServiceRequest = $http;
1209  }
1210 
1211  return $ok;
1212  }
1213 
1223  private function doLTI11Service($type, $url, $xml)
1224  {
1225  $ok = false;
1226  $this->extRequest = '';
1227  $this->extRequestHeaders = '';
1228  $this->extResponse = '';
1229  $this->extResponseHeaders = '';
1230  $this->lastServiceRequest = null;
1231  if (!empty($url)) {
1232  $id = uniqid();
1233  $xmlRequest = <<< EOD
1234 <?xml version = "1.0" encoding = "UTF-8"?>
1235 <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
1236  <imsx_POXHeader>
1237  <imsx_POXRequestHeaderInfo>
1238  <imsx_version>V1.0</imsx_version>
1239  <imsx_messageIdentifier>{$id}</imsx_messageIdentifier>
1240  </imsx_POXRequestHeaderInfo>
1241  </imsx_POXHeader>
1242  <imsx_POXBody>
1243  <{$type}Request>
1244 {$xml}
1245  </{$type}Request>
1246  </imsx_POXBody>
1247 </imsx_POXEnvelopeRequest>
1248 EOD;
1249 // Add message signature
1250  $consumer = $this->getConsumer();
1251  $header = $consumer->addSignature($url, $xmlRequest, 'POST', 'application/xml');
1252 // Connect to tool consumer
1253  $http = new HTTPMessage($url, 'POST', $xmlRequest, $header);
1254 // Parse XML response
1255  if ($http->send()) {
1256  $this->extResponse = $http->response;
1257  $this->extResponseHeaders = $http->responseHeaders;
1258  try {
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')) {
1264  $ok = true;
1265  }
1266  } catch (\Exception $e) {
1267 
1268  }
1269  }
1270  $this->extRequest = $http->request;
1271  $this->extRequestHeaders = $http->requestHeaders;
1272  $this->lastServiceRequest = $http;
1273  }
1274 
1275  return $ok;
1276  }
1277 
1285  private function domnodeToArray($node)
1286  {
1287  $output = '';
1288  switch ($node->nodeType) {
1289  case XML_CDATA_SECTION_NODE:
1290  case XML_TEXT_NODE:
1291  $output = trim($node->textContent);
1292  break;
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;
1299  } else {
1300  $s = (string) $v;
1301  if (strlen($s) > 0) {
1302  $output = $s;
1303  }
1304  }
1305  }
1306  if (is_array($output)) {
1307  if ($node->hasAttributes()) {
1308  foreach ($node->attributes as $attrNode) {
1309  /* @var $attrNode \DOMAttr */
1310  $output['@attributes'][$attrNode->name] = (string) $attrNode->value;
1311  }
1312  }
1313  foreach ($output as $t => $v) {
1314  if (is_array($v) && count($v) == 1 && $t != '@attributes') {
1315  $output[$t] = $v[0];
1316  }
1317  }
1318  }
1319  break;
1320  }
1321 
1322  return $output;
1323  }
1324 
1325 }
Class to implement the Tool Settings service.
static fromResourceLink($resourceLink, $ltiUserId)
Class constructor from resource link.
Definition: UserResult.php:255
Class to provide a connection to a persistent store for LTI objects.
Class to implement a service.
Definition: Service.php:17
static fromRecordId($id, $dataConnector)
Load the context from the database.
Definition: Context.php:467
const MODE_CURRENT_LEVEL
Settings at current level mode.
Class to implement the Membership service.
Definition: Membership.php:17
Class to represent an HTTP message request.
Definition: HTTPMessage.php:16
const ID_SCOPE_RESOURCE
Prefix the ID with the consumer key and resource ID.
Trait to handle API hook registrations.
Definition: ApiHook.php:13
static parseRoles($roles, $ltiVersion=self::LTI_VERSION1)
Get an array of fully qualified user roles.
static fromRecordId($id, $dataConnector)
Load the tool consumer from the database by its record ID.