41 'basic-lti-launch-request' =>
'LtiResourceLinkRequest',
42 'ContentItemSelectionRequest' =>
'LtiDeepLinkingRequest',
43 'ContentItemSelection' =>
'LtiDeepLinkingResponse',
44 'ContentItemUpdateRequest' =>
'LtiDeepLinkingUpdateRequest'
51 'accept_types' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'accept_types',
'isArray' =>
true),
52 'accept_copy_advice' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'copyAdvice',
'isBoolean' =>
true),
53 'accept_media_types' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'accept_media_types'),
54 'accept_multiple' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'accept_multiple',
'isBoolean' =>
true),
55 'accept_presentation_document_targets' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'accept_presentation_document_targets',
'isArray' =>
true),
56 'accept_unsigned' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'accept_unsigned',
'isBoolean' =>
true),
57 'auto_create' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'auto_create',
'isBoolean' =>
true),
58 'can_confirm' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'can_confirm'),
59 'content_item_return_url' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'deep_link_return_url'),
60 'content_items' => array(
'suffix' =>
'dl',
'group' =>
'',
'claim' =>
'content_items',
'isObject' =>
true),
61 'data' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'data'),
62 'data.LtiDeepLinkingResponse' => array(
'suffix' =>
'dl',
'group' =>
'',
'claim' =>
'data'),
63 'text' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'text'),
64 'title' => array(
'suffix' =>
'dl',
'group' =>
'deep_linking_settings',
'claim' =>
'title'),
65 'lti_msg' => array(
'suffix' =>
'dl',
'group' =>
'',
'claim' =>
'msg'),
66 'lti_errormsg' => array(
'suffix' =>
'dl',
'group' =>
'',
'claim' =>
'errormsg'),
67 'lti_log' => array(
'suffix' =>
'dl',
'group' =>
'',
'claim' =>
'log'),
68 'lti_errorlog' => array(
'suffix' =>
'dl',
'group' =>
'',
'claim' =>
'errorlog'),
69 'context_id' => array(
'suffix' =>
'',
'group' =>
'context',
'claim' =>
'id'),
70 'context_label' => array(
'suffix' =>
'',
'group' =>
'context',
'claim' =>
'label'),
71 'context_title' => array(
'suffix' =>
'',
'group' =>
'context',
'claim' =>
'title'),
72 'context_type' => array(
'suffix' =>
'',
'group' =>
'context',
'claim' =>
'type',
'isArray' =>
true),
73 'lis_course_offering_sourcedid' => array(
'suffix' =>
'',
'group' =>
'lis',
'claim' =>
'course_offering_sourcedid'),
74 'lis_course_section_sourcedid' => array(
'suffix' =>
'',
'group' =>
'lis',
'claim' =>
'course_section_sourcedid'),
75 'launch_presentation_css_url' => array(
'suffix' =>
'',
'group' =>
'launch_presentation',
'claim' =>
'css_url'),
76 'launch_presentation_document_target' => array(
'suffix' =>
'',
'group' =>
'launch_presentation',
'claim' =>
'document_target'),
77 'launch_presentation_height' => array(
'suffix' =>
'',
'group' =>
'launch_presentation',
'claim' =>
'height',
'isInteger' =>
true),
78 'launch_presentation_locale' => array(
'suffix' =>
'',
'group' =>
'launch_presentation',
'claim' =>
'locale'),
79 'launch_presentation_return_url' => array(
'suffix' =>
'',
'group' =>
'launch_presentation',
'claim' =>
'return_url'),
80 'launch_presentation_width' => array(
'suffix' =>
'',
'group' =>
'launch_presentation',
'claim' =>
'width',
'isInteger' =>
true),
81 'lis_person_contact_email_primary' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'email'),
82 'lis_person_name_family' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'family_name'),
83 'lis_person_name_full' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'name'),
84 'lis_person_name_given' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'given_name'),
85 'lis_person_name_middle' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'middle_name'),
86 'lis_person_sourcedid' => array(
'suffix' =>
'',
'group' =>
'lis',
'claim' =>
'person_sourcedid'),
87 'user_id' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'sub'),
88 'user_image' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'picture'),
89 'roles' => array(
'suffix' =>
'',
'group' =>
'',
'claim' =>
'roles',
'isArray' =>
true),
90 'role_scope_mentor' => array(
'suffix' =>
'',
'group' =>
'',
'claim' =>
'role_scope_mentor',
'isArray' =>
true),
91 'platform_id' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'iss'),
92 'deployment_id' => array(
'suffix' =>
'',
'group' =>
'',
'claim' =>
'deployment_id'),
93 'lti_message_type' => array(
'suffix' =>
'',
'group' =>
'',
'claim' =>
'message_type'),
94 'lti_version' => array(
'suffix' =>
'',
'group' =>
'',
'claim' =>
'version'),
95 'resource_link_description' => array(
'suffix' =>
'',
'group' =>
'resource_link',
'claim' =>
'description'),
96 'resource_link_id' => array(
'suffix' =>
'',
'group' =>
'resource_link',
'claim' =>
'id'),
97 'resource_link_title' => array(
'suffix' =>
'',
'group' =>
'resource_link',
'claim' =>
'title'),
98 'target_link_uri' => array(
'suffix' =>
'',
'group' =>
'',
'claim' =>
'target_link_uri'),
99 'tool_consumer_info_product_family_code' => array(
'suffix' =>
'',
'group' =>
'tool_platform',
'claim' =>
'product_family_code'),
100 'tool_consumer_info_version' => array(
'suffix' =>
'',
'group' =>
'tool_platform',
'claim' =>
'version'),
101 'tool_consumer_instance_contact_email' => array(
'suffix' =>
'',
'group' =>
'tool_platform',
'claim' =>
'contact_email'),
102 'tool_consumer_instance_description' => array(
'suffix' =>
'',
'group' =>
'tool_platform',
'claim' =>
'description'),
103 'tool_consumer_instance_guid' => array(
'suffix' =>
'',
'group' =>
'tool_platform',
'claim' =>
'guid'),
104 'tool_consumer_instance_name' => array(
'suffix' =>
'',
'group' =>
'tool_platform',
'claim' =>
'name'),
105 'tool_consumer_instance_url' => array(
'suffix' =>
'',
'group' =>
'tool_platform',
'claim' =>
'url'),
106 'for_user_contact_email_primary' => array(
'suffix' =>
'',
'group' =>
'for_user',
'claim' =>
'email'),
107 'for_user_name_family' => array(
'suffix' =>
'',
'group' =>
'for_user',
'claim' =>
'family_name'),
108 'for_user_name_full' => array(
'suffix' =>
'',
'group' =>
'for_user',
'claim' =>
'name'),
109 'for_user_name_given' => array(
'suffix' =>
'',
'group' =>
'for_user',
'claim' =>
'given_name'),
110 'for_user_name_middle' => array(
'suffix' =>
'',
'group' =>
'for_user',
'claim' =>
'middle_name'),
111 'for_user_sourcedid' => array(
'suffix' =>
'',
'group' =>
'for_user',
'claim' =>
'person_sourcedid'),
112 'for_user_id' => array(
'suffix' =>
'',
'group' =>
'for_user',
'claim' =>
'user_id'),
113 'for_user_roles' => array(
'suffix' =>
'',
'group' =>
'for_user',
'claim' =>
'roles',
'isArray' =>
true),
114 'tool_state' => array(
'suffix' =>
'',
'group' =>
'tool',
'claim' =>
'state'),
115 'custom_context_memberships_v2_url' => array(
'suffix' =>
'nrps',
'group' =>
'namesroleservice',
'claim' =>
'context_memberships_url'),
116 'custom_nrps_versions' => array(
'suffix' =>
'nrps',
'group' =>
'namesroleservice',
'claim' =>
'service_versions',
'isArray' =>
true),
117 'custom_lineitems_url' => array(
'suffix' =>
'ags',
'group' =>
'endpoint',
'claim' =>
'lineitems'),
118 'custom_lineitem_url' => array(
'suffix' =>
'ags',
'group' =>
'endpoint',
'claim' =>
'lineitem'),
119 'custom_ags_scopes' => array(
'suffix' =>
'ags',
'group' =>
'endpoint',
'claim' =>
'scope',
'isArray' =>
true),
120 'custom_context_groups_url' => array(
'suffix' =>
'gs',
'group' =>
'groupsservice',
'claim' =>
'context_groups_url'),
121 'custom_context_group_sets_url' => array(
'suffix' =>
'gs',
'group' =>
'groupsservice',
'claim' =>
'context_group_sets_url'),
122 'custom_gs_scopes' => array(
'suffix' =>
'gs',
'group' =>
'groupsservice',
'claim' =>
'scope',
'isArray' =>
true),
123 'custom_gs_versions' => array(
'suffix' =>
'gs',
'group' =>
'groupsservice',
'claim' =>
'service_versions',
'isArray' =>
true),
124 'lis_outcome_service_url' => array(
'suffix' =>
'bo',
'group' =>
'basicoutcome',
'claim' =>
'lis_outcome_service_url'),
125 'lis_result_sourcedid' => array(
'suffix' =>
'bo',
'group' =>
'basicoutcome',
'claim' =>
'lis_result_sourcedid'),
126 'custom_ap_attempt_number' => array(
'suffix' =>
'ap',
'group' =>
'',
'claim' =>
'attempt_number',
'isInteger' =>
true),
127 'custom_ap_start_assessment_url' => array(
'suffix' =>
'ap',
'group' =>
'',
'claim' =>
'start_assessment_url'),
128 'custom_ap_session_data' => array(
'suffix' =>
'ap',
'group' =>
'',
'claim' =>
'session_data'),
129 'custom_ap_acs_actions' => array(
'suffix' =>
'ap',
'group' =>
'acs',
'claim' =>
'actions',
'isArray' =>
true),
130 'custom_ap_acs_url' => array(
'suffix' =>
'ap',
'group' =>
'acs',
'claim' =>
'assessment_control_url'),
131 'custom_ap_proctoring_settings_data' => array(
'suffix' =>
'ap',
'group' =>
'proctoring_settings',
'claim' =>
'data'),
132 'custom_ap_email_verified' => array(
'suffix' =>
'',
'group' =>
null,
'claim' =>
'email_verified',
'isBoolean' =>
true),
133 'custom_ap_verified_user_given_name' => array(
'suffix' =>
'ap',
'group' =>
'verified_user',
'claim' =>
'given_name'),
134 'custom_ap_verified_user_middle_name' => array(
'suffix' =>
'ap',
'group' =>
'verified_user',
'claim' =>
'middle_name'),
135 'custom_ap_verified_user_family_name' => array(
'suffix' =>
'ap',
'group' =>
'verified_user',
'claim' =>
'family_name'),
136 'custom_ap_verified_user_full_name' => array(
'suffix' =>
'ap',
'group' =>
'verified_user',
'claim' =>
'full_name'),
137 'custom_ap_verified_user_image' => array(
'suffix' =>
'ap',
'group' =>
'verified_user',
'claim' =>
'picture'),
138 'custom_ap_end_assessment_return' => array(
'suffix' =>
'ap',
'group' =>
'',
'claim' =>
'end_assessment_return',
'isBoolean' =>
true)
169 public static $LTI_VERSIONS = array(self::LTI_VERSION1, self::LTI_VERSION1P3, self::LTI_VERSION2);
177 'basic-lti-launch-request' =>
'onLaunch',
178 'ConfigureLaunchRequest' =>
'onConfigure',
179 'DashboardRequest' =>
'onDashboard',
180 'ContentItemSelectionRequest' =>
'onContentItem',
181 'ContentItemSelection' =>
'onContentItem',
182 'ContentItemUpdateRequest' =>
'onContentItemUpdate',
183 'LtiSubmissionReviewRequest' =>
'onSubmissionReview',
184 'ToolProxyRegistrationRequest' =>
'onRegister',
185 'LtiStartProctoring' =>
'onLtiStartProctoring',
186 'LtiStartAssessment' =>
'onLtiStartAssessment',
187 'LtiEndAssessment' =>
'onLtiEndAssessment'
218 $isLti = ($_SERVER[
'REQUEST_METHOD'] ===
'POST') &&
219 (!empty($_POST[
'lti_message_type']) || !empty($_POST[
'id_token']) || !empty($_POST[
'JWT']) ||
220 !empty($_POST[
'iss']));
222 $isLti = ($_SERVER[
'REQUEST_METHOD'] ===
'GET') && (!empty($_GET[
'iss']) || !empty($_GET[
'openid_configuration']));
235 if (is_null(self::$requestParameters)) {
236 self::$requestParameters = array_merge($_GET, $_POST);
248 public static function logError($message, $showSource =
true)
250 if (self::$logLevel >= self::LOGLEVEL_ERROR) {
251 self::log(
"[ERROR] {$message}", $showSource);
261 public static function logInfo($message, $showSource =
false)
263 if (self::$logLevel >= self::LOGLEVEL_INFO) {
264 self::log(
"[INFO] {$message}", $showSource);
274 public static function logDebug($message, $showSource =
false)
276 if (self::$logLevel >= self::LOGLEVEL_DEBUG) {
277 self::log(
"[DEBUG] {$message}", $showSource);
294 $message =
"{$_SERVER['REQUEST_METHOD']} request received for '{$_SERVER['REQUEST_URI']}'";
297 $params = OAuth\OAuthUtil::parse_parameters($body);
298 if (!empty($params)) {
299 $message .=
" with body parameters of:\n" . var_export($params,
true);
301 $message .=
" with a body of:\n" . var_export($body,
true);
320 public static function logForm($url, $params, $method =
'POST', $debugLevel =
false)
327 if (self::$logLevel >= self::LOGLEVEL_INFO) {
328 $message =
"Form submitted using {$method} to '{$url}'";
329 if (!empty($params)) {
330 $message .=
" with parameters of:\n" . var_export($params,
true);
332 $message .=
" with no parameters";
348 public static function log($message, $showSource =
false)
352 $backtraces = debug_backtrace();
353 foreach ($backtraces as $backtrace) {
354 if (isset($backtrace[
'file'])) {
355 $source .= PHP_EOL .
" {$backtrace['file']}";
356 if (isset($backtrace[
'line'])) {
357 $source .=
" line {$backtrace['line']}";
361 if (!empty($source)) {
362 $source = PHP_EOL .
"See: {$source}";
365 error_log($message . $source);
378 public static function sendForm($url, $params, $target =
'', $javascript =
'')
380 $timeout = static::$formSubmissionTimeout;
381 if (empty($javascript)) {
382 $javascript = <<< EOD
383function doUnblock() {
384 var el = document.getElementById(
'id_blocked');
385 el.style.display =
'block';
389 if ((document.forms[0].target ===
'_blank') && (window.top === window.self)) {
390 document.forms[0].target =
'';
392 window.setTimeout(doUnblock, {$timeout}000);
393 document.forms[0].submit();
396window.onload=doOnLoad;
403<title>1EdTech LTI message</title>
404<script type=
"text/javascript">
409 <form action=
"{$url}" method=
"post" target=
"{$target}" encType=
"application/x-www-form-urlencoded">
410 <p
id=
"id_blocked" style=
"display: none; color: red; font-weight: bold;">
411 Your browser may be blocking
this request;
try clicking the button below.<br><br>
412 <input type=
"submit" value=
"Continue" />
416 if (!empty($params)) {
417 foreach ($params as $key => $value) {
418 $key = htmlentities($key, ENT_COMPAT | ENT_HTML401,
'UTF-8');
419 if (!is_array($value)) {
420 $value = htmlentities($value, ENT_COMPAT | ENT_HTML401,
'UTF-8');
422 <input type=
"hidden" name=
"{$key}" id=
"id_{$key}" value=
"{$value}" />
426 foreach ($value as $element) {
427 $element = htmlentities($element, ENT_COMPAT | ENT_HTML401,
'UTF-8');
429 <input type=
"hidden" name=
"{$key}" value=
"{$element}" />
456 if (!empty($params)) {
457 if (strpos($url,
'?') ===
false) {
463 foreach ($params as $key => $value) {
464 $key = urlencode($key);
465 if (!is_array($value)) {
466 $value = urlencode($value);
467 $url .=
"{$sep}{$key}={$value}";
470 foreach ($value as $element) {
471 $element = urlencode($element);
472 $url .=
"{$sep}{$key}={$element}";
479 header(
"Location: {$url}");
490 if (!$delete || isset($_COOKIE[self::TEST_COOKIE_NAME])) {
491 $oauthRequest = OAuth\OAuthRequest::from_request();
492 $url = $oauthRequest->get_normalized_http_url();
493 $secure = (parse_url($url, PHP_URL_SCHEME) ===
'https');
494 $path = parse_url($url, PHP_URL_PATH);
497 } elseif (substr($path, -1) ==
'/') {
498 $path = substr($path, 0, -1);
503 $expires = time() - 3600;
505 if ((PHP_MAJOR_VERSION > 7) || ((PHP_MAJOR_VERSION >= 7) && (PHP_MINOR_VERSION >= 3))) {
506 setcookie(self::TEST_COOKIE_NAME,
'LTI cookie check',
507 array(
'expires' => $expires,
'path' => $path,
'domain' => $_SERVER[
'HTTP_HOST'],
'secure' => $secure,
'httponly' =>
true,
'SameSite' =>
'None'));
509 setcookie(self::TEST_COOKIE_NAME,
'LTI cookie check', $expires, $path, $_SERVER[
'HTTP_HOST'], $secure);
525 $chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
528 $charsLength = strlen($chars) - 1;
530 for ($i = 1; $i <= $length; $i++) {
531 $value .= $chars[rand(0, $charsLength)];
546 $html = strip_tags($html);
547 $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML401);
560 public static function jsonDecode($str, $associative =
false)
563 $json = \json_decode($str, $associative);
581 $objVars = get_object_vars($clone);
582 foreach ($objVars as $attrName => $attrValue) {
583 if (is_object($clone->$attrName)) {
585 }
else if (is_array($clone->$attrName)) {
586 foreach ($clone->$attrName as &$attrArrayValue) {
587 if (is_object($attrArrayValue)) {
590 unset($attrArrayValue);
static $POST_INPUT
Access to POST data.
Class to implement utility methods.
static sendForm($url, $params, $target='', $javascript='')
Generate a web page containing an auto-submitted form of parameters.
const TEST_COOKIE_NAME
Name of test cookie.
static log($message, $showSource=false)
Log an error message irrespective of the logging level.
const LTI_VERSION1P3
LTI version 1.3 for messages.
const JWT_CLAIM_MAPPING
Mapping for standard message parameters to JWT claim.
const MESSAGE_TYPE_MAPPING
Mapping for standard message types.
const LOGLEVEL_INFO
Log error and information messages.
static stripHtml($html)
Strip HTML tags from a string.
static getRandomString($length=8)
Generate a random string.
static cloneObject($obj)
Clone an object and any objects it contains.
static logRequest($debugLevel=false)
Log a request received.
static isLtiMessage()
Check whether the request received could be an LTI message.
static $METHOD_NAMES
List of supported message types and associated class methods.
static logInfo($message, $showSource=false)
Log an information message.
static $LTI_VERSIONS
Permitted LTI versions for messages.
static $logLevel
Current logging level.
static $requestParameters
GET and POST request parameters.
static logError($message, $showSource=true)
Log an error message.
const LOGLEVEL_NONE
No logging.
static redirect($url, $params)
Redirect to a URL with query parameters.
static getRequestParameters()
Return GET and POST request parameters (POST parameters take precedence).
const LTI_VERSION1
LTI version 1 for messages.
static $formSubmissionTimeout
Delay (in seconds) before a manual button is displayed in case a browser is blocking a form submissio...
const LOGLEVEL_DEBUG
Log all messages.
static jsonDecode($str, $associative=false)
Decode a JSON string.
const LOGLEVEL_ERROR
Log errors only.
static logForm($url, $params, $method='POST', $debugLevel=false)
Log a form submission.
static setTestCookie($delete=false)
Set or delete a test cookie.
const JWT_CLAIM_PREFIX
Prefix for standard JWT message claims.
static logDebug($message, $showSource=false)
Log a debug message.
const LTI_VERSION2
LTI version 2 for messages.