diff --git a/src/lti/LTI_Course_Groups_Service.php b/src/lti/LTI_Course_Groups_Service.php new file mode 100644 index 00000000..b9a878c3 --- /dev/null +++ b/src/lti/LTI_Course_Groups_Service.php @@ -0,0 +1,110 @@ +service_connector = $service_connector; + $this->service_data = $service_data; + } + + public function get_groups() { + + $groups = []; + + $next_page = $this->service_data['context_groups_url']; + + while ($next_page) { + $page = $this->service_connector->make_service_request( + $this->service_data['scope'], + 'GET', + $next_page, + null, + null, + 'application/vnd.ims.lti-gs.v1.contextgroupcontainer+json' + ); + + $groups = array_merge($groups, $page['body']['groups']); + + $next_page = false; + foreach($page['headers'] as $header) { + if (preg_match(LTI_Service_Connector::NEXT_PAGE_REGEX, $header, $matches)) { + $next_page = $matches[1]; + break; + } + } + } + return $groups; + + } + + public function get_sets() { + + $sets = []; + + // Sets are optional. + if (!isset($this->service_data['context_group_sets_url'])) { + return []; + } + + $next_page = $this->service_data['context_group_sets_url']; + + while ($next_page) { + $page = $this->service_connector->make_service_request( + $this->service_data['scope'], + 'GET', + $next_page, + null, + null, + 'application/vnd.ims.lti-gs.v1.contextgroupcontainer+json' + ); + + $sets = array_merge($sets, $page['body']['sets']); + + $next_page = false; + foreach($page['headers'] as $header) { + if (preg_match(LTI_Service_Connector::NEXT_PAGE_REGEX, $header, $matches)) { + $next_page = $matches[1]; + break; + } + } + } + return $sets; + + } + + public function get_groups_by_set() { + $groups = $this->get_groups(); + $sets = $this->get_sets(); + + $groups_by_set = []; + $unsetted = []; + + foreach ($sets as $key => $set) { + $groups_by_set[$set['id']] = $set; + $groups_by_set[$set['id']]['groups'] = []; + } + + foreach ($groups as $key => $group) { + if (isset($group['set_id']) && isset($groups_by_set[$group['set_id']])) { + $groups_by_set[$group['set_id']]['groups'][$group['id']] = $group; + } else { + $unsetted[$group['id']] = $group; + } + } + + if (!empty($unsetted)) { + $groups_by_set['none'] = [ + "name" => "None", + "id" => "none", + "groups" => $unsetted, + ]; + } + + return $groups_by_set; + } +} +?> \ No newline at end of file diff --git a/src/lti/LTI_Grade.php b/src/lti/LTI_Grade.php index 75e7ab2a..fabeb6ad 100644 --- a/src/lti/LTI_Grade.php +++ b/src/lti/LTI_Grade.php @@ -4,10 +4,12 @@ class LTI_Grade { private $score_given; private $score_maximum; + private $comment; private $activity_progress; private $grading_progress; private $timestamp; private $user_id; + private $submission_review; /** * Static function to allow for method chaining without having to assign to a variable first. @@ -34,6 +36,15 @@ public function set_score_maximum($value) { return $this; } + public function get_comment() { + return $this->comment; + } + + public function set_comment($comment) { + $this->comment = $comment; + return $this; + } + public function get_activity_progress() { return $this->activity_progress; } @@ -70,14 +81,25 @@ public function set_user_id($value) { return $this; } + public function get_submission_review() { + return $this->submission_review; + } + + public function set_submission_review($value) { + $this->submission_review = $value; + return $this; + } + public function __toString() { return json_encode(array_filter([ "scoreGiven" => 0 + $this->score_given, "scoreMaximum" => 0 + $this->score_maximum, + "comment" => $this->comment, "activityProgress" => $this->activity_progress, "gradingProgress" => $this->grading_progress, "timestamp" => $this->timestamp, "userId" => $this->user_id, + "submissionReview" => $this->submission_review, ])); } } diff --git a/src/lti/LTI_Grade_Submission_Review.php b/src/lti/LTI_Grade_Submission_Review.php new file mode 100644 index 00000000..73fc073f --- /dev/null +++ b/src/lti/LTI_Grade_Submission_Review.php @@ -0,0 +1,62 @@ +reviewable_status; + } + + public function set_reviewable_status($value) { + $this->reviewable_status = $value; + return $this; + } + + public function get_label() { + return $this->label; + } + + public function set_label($value) { + $this->label = $value; + return $this; + } + + public function get_url() { + return $this->url; + } + + public function set_url($url) { + $this->url = $url; + return $this; + } + + public function get_custom() { + return $this->custom; + } + + public function set_custom($value) { + $this->custom = $value; + return $this; + } + + public function __toString() { + return json_encode(array_filter([ + "reviewableStatus" => $this->reviewable_status, + "label" => $this->label, + "url" => $this->url, + "custom" => $this->custom, + ])); + } +} +?> \ No newline at end of file diff --git a/src/lti/LTI_Message_Launch.php b/src/lti/LTI_Message_Launch.php index 5dd9b6f1..7a18195d 100644 --- a/src/lti/LTI_Message_Launch.php +++ b/src/lti/LTI_Message_Launch.php @@ -108,6 +108,26 @@ public function get_nrps() { $this->jwt['body']['https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice']); } + /** + * Returns whether or not the current launch can use the groups service. + * + * @return boolean Returns a boolean indicating the availability of groups. + */ + public function has_gs() { + return !empty($this->jwt['body']['https://purl.imsglobal.org/spec/lti-gs/claim/groupsservice']['context_groups_url']); + } + + /** + * Fetches an instance of the groups service for the current launch. + * + * @return LTI_Course_Groups_Service An instance of the groups service that can be used to make calls within the scope of the current launch. + */ + public function get_gs() { + return new LTI_Course_Groups_Service( + new LTI_Service_Connector($this->registration), + $this->jwt['body']['https://purl.imsglobal.org/spec/lti-gs/claim/groupsservice']); + } + /** * Returns whether or not the current launch can use the assignments and grades service. * @@ -149,6 +169,15 @@ public function is_deep_link_launch() { return $this->jwt['body']['https://purl.imsglobal.org/spec/lti/claim/message_type'] === 'LtiDeepLinkingRequest'; } + /** + * Returns whether or not the current launch is a submission review launch. + * + * @return boolean Returns true if the current launch is a submission review launch. + */ + public function is_submission_review_launch() { + return $this->jwt['body']['https://purl.imsglobal.org/spec/lti/claim/message_type'] === 'LtiSubmissionReviewRequest'; + } + /** * Returns whether or not the current launch is a resource launch. * diff --git a/src/lti/LTI_Names_Roles_Provisioning_Service.php b/src/lti/LTI_Names_Roles_Provisioning_Service.php index 86df669c..df2242ea 100644 --- a/src/lti/LTI_Names_Roles_Provisioning_Service.php +++ b/src/lti/LTI_Names_Roles_Provisioning_Service.php @@ -31,7 +31,7 @@ public function get_members() { $next_page = false; foreach($page['headers'] as $header) { - if (preg_match("/^Link:.*<([^>]*)>; ?rel=\"next\"/i", $header, $matches)) { + if (preg_match(LTI_Service_Connector::NEXT_PAGE_REGEX, $header, $matches)) { $next_page = $matches[1]; break; } diff --git a/src/lti/LTI_Service_Connector.php b/src/lti/LTI_Service_Connector.php index 1c8d9115..cd2318e5 100644 --- a/src/lti/LTI_Service_Connector.php +++ b/src/lti/LTI_Service_Connector.php @@ -4,6 +4,9 @@ use Firebase\JWT\JWT; class LTI_Service_Connector { + + const NEXT_PAGE_REGEX = "/^Link:.*<([^>]*)>; ?rel=\"next\"/i"; + private $registration; private $access_tokens = []; diff --git a/src/lti/message_validators/submission_review_message_validator.php b/src/lti/message_validators/submission_review_message_validator.php new file mode 100644 index 00000000..60cc4914 --- /dev/null +++ b/src/lti/message_validators/submission_review_message_validator.php @@ -0,0 +1,29 @@ + \ No newline at end of file