LTI Integration Library 4.10.3
PHP class library for building LTI integrations
 
Loading...
Searching...
No Matches
Util.php
1<?php
2
3namespace ceLTIc\LTI;
4
5use ceLTIc\LTI\OAuth;
6
14final class Util
15{
16
20 const LTI_VERSION1 = 'LTI-1p0';
21
25 const LTI_VERSION1P3 = '1.3.0';
26
30 const LTI_VERSION2 = 'LTI-2p0';
31
35 const JWT_CLAIM_PREFIX = 'https://purl.imsglobal.org/spec/lti';
36
40 const MESSAGE_TYPE_MAPPING = array(
41 'basic-lti-launch-request' => 'LtiResourceLinkRequest',
42 'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest',
43 'ContentItemSelection' => 'LtiDeepLinkingResponse',
44 'ContentItemUpdateRequest' => 'LtiDeepLinkingUpdateRequest'
45 );
46
50 const JWT_CLAIM_MAPPING = array(
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)
139 );
140
144 const LOGLEVEL_NONE = 0;
145
149 const LOGLEVEL_ERROR = 1;
150
154 const LOGLEVEL_INFO = 2;
155
159 const LOGLEVEL_DEBUG = 3;
160
164 const TEST_COOKIE_NAME = 'celtic_lti_test_cookie';
165
169 public static $LTI_VERSIONS = array(self::LTI_VERSION1, self::LTI_VERSION1P3, self::LTI_VERSION2);
170
176 public static $METHOD_NAMES = array(
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'
188 );
189
195 public static $requestParameters = null;
196
203
209 public static $formSubmissionTimeout = 2;
210
216 public static function isLtiMessage()
217 {
218 $isLti = ($_SERVER['REQUEST_METHOD'] === 'POST') &&
219 (!empty($_POST['lti_message_type']) || !empty($_POST['id_token']) || !empty($_POST['JWT']) ||
220 !empty($_POST['iss']));
221 if (!$isLti) {
222 $isLti = ($_SERVER['REQUEST_METHOD'] === 'GET') && (!empty($_GET['iss']) || !empty($_GET['openid_configuration']));
223 }
224
225 return $isLti;
226 }
227
233 public static function getRequestParameters()
234 {
235 if (is_null(self::$requestParameters)) {
236 self::$requestParameters = array_merge($_GET, $_POST);
237 }
238
240 }
241
248 public static function logError($message, $showSource = true)
249 {
250 if (self::$logLevel >= self::LOGLEVEL_ERROR) {
251 self::log("[ERROR] {$message}", $showSource);
252 }
253 }
254
261 public static function logInfo($message, $showSource = false)
262 {
263 if (self::$logLevel >= self::LOGLEVEL_INFO) {
264 self::log("[INFO] {$message}", $showSource);
265 }
266 }
267
274 public static function logDebug($message, $showSource = false)
275 {
276 if (self::$logLevel >= self::LOGLEVEL_DEBUG) {
277 self::log("[DEBUG] {$message}", $showSource);
278 }
279 }
280
286 public static function logRequest($debugLevel = false)
287 {
288 if (!$debugLevel) {
290 } else {
292 }
293 if (self::$logLevel >= $logLevel) {
294 $message = "{$_SERVER['REQUEST_METHOD']} request received for '{$_SERVER['REQUEST_URI']}'";
295 $body = file_get_contents(OAuth\OAuthRequest::$POST_INPUT);
296 if (!empty($body)) {
297 $params = OAuth\OAuthUtil::parse_parameters($body);
298 if (!empty($params)) {
299 $message .= " with body parameters of:\n" . var_export($params, true);
300 } else {
301 $message .= " with a body of:\n" . var_export($body, true);
302 }
303 }
304 if (!$debugLevel) {
305 self::logInfo($message);
306 } else {
307 self::logDebug($message);
308 }
309 }
310 }
311
320 public static function logForm($url, $params, $method = 'POST', $debugLevel = false)
321 {
322 if (!$debugLevel) {
324 } else {
326 }
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);
331 } else {
332 $message .= " with no parameters";
333 }
334 if ($logLevel < self::LOGLEVEL_DEBUG) {
335 self::logInfo($message);
336 } else {
337 self::logDebug($message);
338 }
339 }
340 }
341
348 public static function log($message, $showSource = false)
349 {
350 $source = '';
351 if ($showSource) {
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']}";
358 }
359 }
360 }
361 if (!empty($source)) {
362 $source = PHP_EOL . "See: {$source}";
363 }
364 }
365 error_log($message . $source);
366 }
367
378 public static function sendForm($url, $params, $target = '', $javascript = '')
379 {
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';
386}
387
388function doOnLoad() {
389 if ((document.forms[0].target === '_blank') && (window.top === window.self)) {
390 document.forms[0].target = '';
391 }
392 window.setTimeout(doUnblock, {$timeout}000);
393 document.forms[0].submit();
394}
395
396window.onload=doOnLoad;
397EOD;
398 }
399 self::logForm($url, $params, 'POST');
400 $page = <<< EOD
401<!DOCTYPE html>
402<head>
403<title>1EdTech LTI message</title>
404<script type="text/javascript">
405{$javascript}
406</script>
407</head>
408<body>
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" />
413 </p>
414
415EOD;
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');
421 $page .= <<< EOD
422 <input type="hidden" name="{$key}" id="id_{$key}" value="{$value}" />
423
424EOD;
425 } else {
426 foreach ($value as $element) {
427 $element = htmlentities($element, ENT_COMPAT | ENT_HTML401, 'UTF-8');
428 $page .= <<< EOD
429 <input type="hidden" name="{$key}" value="{$element}" />
430
431EOD;
432 }
433 }
434 }
435 }
436
437 $page .= <<< EOD
438 </form>
439</body>
440</html>
441EOD;
442
443 return $page;
444 }
445
454 public static function redirect($url, $params)
455 {
456 if (!empty($params)) {
457 if (strpos($url, '?') === false) {
458 $url .= '?';
459 $sep = '';
460 } else {
461 $sep = '&';
462 }
463 foreach ($params as $key => $value) {
464 $key = urlencode($key);
465 if (!is_array($value)) {
466 $value = urlencode($value);
467 $url .= "{$sep}{$key}={$value}";
468 $sep = '&';
469 } else {
470 foreach ($value as $element) {
471 $element = urlencode($element);
472 $url .= "{$sep}{$key}={$element}";
473 $sep = '&';
474 }
475 }
476 }
477 }
478
479 header("Location: {$url}");
480 exit;
481 }
482
488 public static function setTestCookie($delete = false)
489 {
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);
495 if (empty($path)) {
496 $path = '/';
497 } elseif (substr($path, -1) == '/') {
498 $path = substr($path, 0, -1);
499 }
500 if (!$delete) {
501 $expires = 0;
502 } else {
503 $expires = time() - 3600;
504 }
505 if ((PHP_MAJOR_VERSION > 7) || ((PHP_MAJOR_VERSION >= 7) && (PHP_MINOR_VERSION >= 3))) { // PHP 7.3 or later?
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'));
508 } else {
509 setcookie(self::TEST_COOKIE_NAME, 'LTI cookie check', $expires, $path, $_SERVER['HTTP_HOST'], $secure);
510 }
511 }
512 }
513
523 public static function getRandomString($length = 8)
524 {
525 $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
526
527 $value = '';
528 $charsLength = strlen($chars) - 1;
529
530 for ($i = 1; $i <= $length; $i++) {
531 $value .= $chars[rand(0, $charsLength)];
532 }
533
534 return $value;
535 }
536
544 public static function stripHtml($html)
545 {
546 $html = strip_tags($html);
547 $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML401);
548
549 return $html;
550 }
551
560 public static function jsonDecode($str, $associative = false)
561 {
562 if (!empty($str)) {
563 $json = \json_decode($str, $associative);
564 } else {
565 $json = null;
566 }
567
568 return $json;
569 }
570
578 public static function cloneObject($obj)
579 {
580 $clone = clone $obj;
581 $objVars = get_object_vars($clone);
582 foreach ($objVars as $attrName => $attrValue) {
583 if (is_object($clone->$attrName)) {
584 $clone->$attrName = self::cloneObject($clone->$attrName);
585 } else if (is_array($clone->$attrName)) {
586 foreach ($clone->$attrName as &$attrArrayValue) {
587 if (is_object($attrArrayValue)) {
588 $attrArrayValue = self::cloneObject($attrArrayValue);
589 }
590 unset($attrArrayValue);
591 }
592 }
593 }
594
595 return $clone;
596 }
597
598}
static $POST_INPUT
Access to POST data.
Class to implement utility methods.
Definition Util.php:15
static sendForm($url, $params, $target='', $javascript='')
Generate a web page containing an auto-submitted form of parameters.
Definition Util.php:378
const TEST_COOKIE_NAME
Name of test cookie.
Definition Util.php:164
static log($message, $showSource=false)
Log an error message irrespective of the logging level.
Definition Util.php:348
const LTI_VERSION1P3
LTI version 1.3 for messages.
Definition Util.php:25
const JWT_CLAIM_MAPPING
Mapping for standard message parameters to JWT claim.
Definition Util.php:50
const MESSAGE_TYPE_MAPPING
Mapping for standard message types.
Definition Util.php:40
const LOGLEVEL_INFO
Log error and information messages.
Definition Util.php:154
static stripHtml($html)
Strip HTML tags from a string.
Definition Util.php:544
static getRandomString($length=8)
Generate a random string.
Definition Util.php:523
static cloneObject($obj)
Clone an object and any objects it contains.
Definition Util.php:578
static logRequest($debugLevel=false)
Log a request received.
Definition Util.php:286
static isLtiMessage()
Check whether the request received could be an LTI message.
Definition Util.php:216
static $METHOD_NAMES
List of supported message types and associated class methods.
Definition Util.php:176
static logInfo($message, $showSource=false)
Log an information message.
Definition Util.php:261
static $LTI_VERSIONS
Permitted LTI versions for messages.
Definition Util.php:169
static $logLevel
Current logging level.
Definition Util.php:202
static $requestParameters
GET and POST request parameters.
Definition Util.php:195
static logError($message, $showSource=true)
Log an error message.
Definition Util.php:248
const LOGLEVEL_NONE
No logging.
Definition Util.php:144
static redirect($url, $params)
Redirect to a URL with query parameters.
Definition Util.php:454
static getRequestParameters()
Return GET and POST request parameters (POST parameters take precedence).
Definition Util.php:233
const LTI_VERSION1
LTI version 1 for messages.
Definition Util.php:20
static $formSubmissionTimeout
Delay (in seconds) before a manual button is displayed in case a browser is blocking a form submissio...
Definition Util.php:209
const LOGLEVEL_DEBUG
Log all messages.
Definition Util.php:159
static jsonDecode($str, $associative=false)
Decode a JSON string.
Definition Util.php:560
const LOGLEVEL_ERROR
Log errors only.
Definition Util.php:149
static logForm($url, $params, $method='POST', $debugLevel=false)
Log a form submission.
Definition Util.php:320
static setTestCookie($delete=false)
Set or delete a test cookie.
Definition Util.php:488
const JWT_CLAIM_PREFIX
Prefix for standard JWT message claims.
Definition Util.php:35
static logDebug($message, $showSource=false)
Log a debug message.
Definition Util.php:274
const LTI_VERSION2
LTI version 2 for messages.
Definition Util.php:30