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.