<?
require_once("gradebookservices.php");
require 'vendor/autoload.php'; // Include the JWT library
use Firebase\JWT\JWT;

/** Read-only access to Gradebook services */
const GRADEBOOKSERVICES_READ = 1;
/** Full access to Gradebook services */
const GRADEBOOKSERVICES_FULL = 2;
/** Scope for full access to Lineitem service */
const SCOPE_GRADEBOOKSERVICES_LINEITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem';
/** Scope for full access to Lineitem service */
const SCOPE_GRADEBOOKSERVICES_LINEITEM_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly';
/** Scope for access to Result service */
const SCOPE_GRADEBOOKSERVICES_RESULT_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly';
/** Scope for access to Score service */
const SCOPE_GRADEBOOKSERVICES_SCORE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score';
/**
 * Generate the form for initiating a login request for an LTI 1.3 message
 *
 * @param int            $courseid  Course ID
 * @param int            $contentdtl_id        LTI instance ID (Contentdtl id)
 * @param stdClass|null  $instance  LTI instance 
 * @param stdClass       $config    Tool type configuration (LTI Types)
 * @param string         $messagetype   LTI message type
 * @param string         $title     Title of content item
 * @param string         $text      Description of content item
 * @param int            $foruserid Id of the user targeted by the launch
 * @return string
 */
function lti_initiate_login(
    $uid,
    $courseid,
    $comp_id,
    $contentdtl_id,
    $offering_id,
    $instance,
    $config,
    $messagetype = 'basic-lti-launch-request',
    $title = '',
    $text = '',
    $foruserid = 0
) {

    $params = lti_build_login_request($uid, $courseid, $comp_id, $contentdtl_id, $offering_id, $instance, $config, $messagetype, $foruserid, $title, $text);

    $r = "<form action=\"" . $config->lti_initiatelogin .
        "\" name=\"ltiInitiateLoginForm\" id=\"ltiInitiateLoginForm\" method=\"post\" " .
        "encType=\"application/x-www-form-urlencoded\">\n";

    foreach ($params as $key => $value) {
        $key = htmlspecialchars($key, ENT_COMPAT);
        $value = htmlspecialchars($value, ENT_COMPAT);
        $r .= "  <input type=\"hidden\" name=\"{$key}\" value=\"{$value}\"/>\n";
    }
    $r .= "</form>\n";

    $r .= "<script type=\"text/javascript\">\n" .
        "//<![CDATA[\n" .
        "document.ltiInitiateLoginForm.submit();\n" .
        "//]]>\n" .
        "</script>\n";

    return $r;
}

/**
 * Prepares an LTI 1.3 login request
 *
 * @param int            $courseid  Course ID
 * @param int            $contentdtl_id        Course Module instance ID
 * @param stdClass|null  $instance  LTI instance
 * @param stdClass       $config    Tool type configuration
 * @param string         $messagetype   LTI message type
 * @param int            $foruserid Id of the user targeted by the launch
 * @param string         $title     Title of content item
 * @param string         $text      Description of content item
 * @return array Login request parameters
 */
function lti_build_login_request($uid, $courseid, $comp_id, $contentdtl_id, $offering_id, $instance, $config, $messagetype, $foruserid = 0, $title = '', $text = '')
{
    global $SESSION;
    $ltihint = [];
    if (!empty($instance)) {
        $endpoint = !empty($instance->lti_toolurl) ? $instance->lti_toolurl : $config->lti_toolurl;
        $launchid = 'ltilaunch' . $instance->lti_types_id . '_' . rand();
        $ltihint['contentdtl_id'] = $contentdtl_id;
        $SESSION->$launchid = "{$uid},{$courseid},{$comp_id},{$config->typeid},{$contentdtl_id},{$offering_id},{$messagetype},{$foruserid},,";
        $_SESSION[$launchid] = $SESSION;
    } else {
        $endpoint = $config->lti_toolurl;
        if (($messagetype === 'ContentItemSelectionRequest') && !empty($config->lti_toolurl_ContentItemSelectionRequest)) {
            $endpoint = $config->lti_toolurl_ContentItemSelectionRequest;
        }
        $launchid = "ltilaunch_$messagetype" . rand();
        $SESSION->$launchid =
            "{$uid},{$courseid},{$comp_id},{$config->typeid},,,{$messagetype},{$foruserid}," . base64_encode($title) . ',' . base64_encode($text);
        $_SESSION[$launchid] = $SESSION;
    }
    $endpoint = trim($endpoint);

    //Tip: Only for LtiSubmissionReviewRequest (Next version)
    // $services = lti_get_services();
    // foreach ($services as $service) {
    //     [$endpoint] = $service->override_endpoint($messagetype ?? 'basic-lti-launch-request', $endpoint, '', $courseid, $instance);
    // }

    $ltihint['launchid'] = $launchid;
    //Tip: now not checking https or http (Next version)
    // // If SSL is forced make sure https is on the normal launch URL.
    // if (isset($config->lti_forcessl) && ($config->lti_forcessl == '1')) {
    //     $endpoint = lti_ensure_url_is_https($endpoint);
    // } else if (!strstr($endpoint, '://')) {
    //     $endpoint = 'http://' . $endpoint;
    // }

    $params = array();
    $params['iss'] = CLIENT_HTTP_PATH;
    $params['target_link_uri'] = $endpoint;
    $params['login_hint'] = $uid;
    $params['lti_message_hint'] = json_encode($ltihint);
    $params['client_id'] = $config->lti_clientid;
    $params['lti_deployment_id'] = $config->typeid;
    return $params;
}

/**
 * Builds a standard LTI Content-Item selection request.
 *
 * @param stdClass $type The tool type and type config.
 * @param stdClass $course The course object.
 * @param string $returnurl The return URL in the tool consumer (TC) that the tool provider (TP)
 *                              will use to return the Content-Item message.
 * @param string $title The tool's title, if available.
 * @param string $text The text to display to represent the content item. This value may be a long description of the content item.
 * @param array $mediatypes Array of MIME types types supported by the TC. If empty, the TC will support ltilink by default.
 * @param array $presentationtargets Array of ways in which the selected content item(s) can be requested to be opened
 *                                   (via the presentationDocumentTarget element for a returned content item).
 *                                   If empty, "frame", "iframe", and "window" will be supported by default.
 * @param bool $autocreate Indicates whether any content items returned by the TP would be automatically persisted without
 * @param bool $multiple Indicates whether the user should be permitted to select more than one item. False by default.
 *                         any option for the user to cancel the operation. False by default.
 * @param bool $unsigned Indicates whether the TC is willing to accept an unsigned return message, or not.
 *                       A signed message should always be required when the content item is being created automatically in the
 *                       TC without further interaction from the user. False by default.
 * @param bool $canconfirm Flag for can_confirm parameter. False by default.
 * @param bool $copyadvice Indicates whether the TC is able and willing to make a local copy of a content item. False by default.
 * @param string $nonce
 * @return stdClass The object containing the signed request parameters and the URL to the TP's Content-Item selection interface.
 * @throws Exception When the LTI tool type does not exist and for invalid media type and presentation target parameters.
 */
function lti_build_content_item_selection_request(
    $user,
    $company,
    $instance,
    $type,
    $course,
    $returnurl,
    $title = '',
    $text = '',
    $mediatypes = [],
    $presentationtargets = [],
    $autocreate = false,
    $multiple = false,
    $unsigned = false,
    $canconfirm = false,
    $copyadvice = false,
    $nonce = ''
) {
    if (!$type) {
        throw new Exception('errortooltypenotfound', 'mod_lti');
    }
    if (!is_array($mediatypes)) {
        throw new Exception('The list of accepted media types should be in an array');
    }
    if (!is_array($presentationtargets)) {
        throw new Exception('The list of accepted presentation targets should be in an array');
    }

    // Check title. If empty, use the tool's name.
    if (empty($title)) {
        $title = $type->lti_typename;
    }

    $key = '';
    $secret = '';
    $islti13 = false;

    $islti13 = $type->lti_ltiversion === LTI_VERSION_1P3;
    $toolproxy = null;
    // if ($islti13 && !empty($tool->clientid)) {
    $key = $type->lti_clientid;
    // }
    //Tip: Check for resource key and password (Next version)
    // if (!empty($typeconfig['password'])) {
    //     $secret = $typeconfig['password'];
    // }

    $type->enabledcapability = '';
    if (!empty($type->enabledcapability_ContentItemSelectionRequest)) {
        $type->enabledcapability = $type->enabledcapability_ContentItemSelectionRequest;
    }

    $type->parameter = '';
    if (!empty($type->parameter_ContentItemSelectionRequest)) {
        $type->parameter = $type->parameter_ContentItemSelectionRequest;
    }
    // Set the tool URL.
    if (!empty($type->toolurl_ContentItemSelectionRequest)) {
        $toolurl = $type->toolurl_ContentItemSelectionRequest;
    } else {
        $toolurl = $type->lti_toolurl;
    }

    $requestparams = lti_build_request($user, $company, $instance, $type, $course, null);

    $standardparams = lti_build_standard_message($user, $company, $instance, $type->lti_ltiversion, 'ContentItemSelectionRequest');
    $requestparams = array_merge($requestparams, $standardparams);

    // Get custom request parameters and merge to the request parameters.
    $customstr = '';
    if (!empty($type->customparameters)) {
        $customstr = $type->customparameters;
    }
    //Tip:Not implemented (Next version)
    // $customparams = lti_build_custom_parameters($toolproxy, $type, $instance, $requestparams, $customstr, '');
    // $requestparams = array_merge($requestparams, $customparams);

    //Check for service
    $serviceparameters = get_launch_parameters(
        'ContentItemSelectionRequest',
        $user,
        $type,
    );
    foreach ($serviceparameters as $paramkey => $paramvalue) {
        $requestparams['custom_' . $paramkey] = lti_parse_custom_parameter(
            $toolproxy,
            $type,
            $offering_id = 0,
            $contentdtl_id = 0,
            $requestparams,
            $paramvalue
        );
    }

    //Tip: ltisource not implementd(Not sure require or not, Next version)
    // $plugins = core_component::get_plugin_list('ltisource');
    // foreach (array_keys($plugins) as $plugin) {
    //     $pluginparams = component_callback('ltisource_' . $plugin, 'before_launch', [$instance, $toolurlout, $requestparams], []);

    //     if (!empty($pluginparams) && is_array($pluginparams)) {
    //         $requestparams = array_merge($requestparams, $pluginparams);
    //     }
    // }

    // Only LTI links are currently supported.
    $requestparams['accept_types'] = 'ltiResourceLink';

    if (empty($presentationtargets)) {
        $presentationtargets = [
            'frame',
            'iframe',
            'window',
        ];
    }
    $requestparams['accept_presentation_document_targets'] = implode(',', $presentationtargets);

    // Other request parameters.
    $requestparams['accept_copy_advice'] = $copyadvice === true ? 'true' : 'false';
    $requestparams['accept_multiple'] = $multiple === true ? 'true' : 'false';
    $requestparams['accept_unsigned'] = $unsigned === true ? 'true' : 'false';
    $requestparams['auto_create'] = $autocreate === true ? 'true' : 'false';
    $requestparams['can_confirm'] = $canconfirm === true ? 'true' : 'false';
    $requestparams['content_item_return_url'] = $returnurl;
    $requestparams['title'] = $title;
    $requestparams['text'] = $text;
    $signedparams = lti_sign_jwt($company, $requestparams, $toolurl, $key, $type->typeid, $nonce);

    // Check for params that should not be passed. Unset if they are set.
    $unwantedparams = [
        'resource_link_id',
        'resource_link_title',
        'resource_link_description',
        'launch_presentation_return_url',
        'lis_result_sourcedid',
    ];
    foreach ($unwantedparams as $param) {
        if (isset($signedparams[$param])) {
            unset($signedparams[$param]);
        }
    }

    // Prepare result object.
    $result = new stdClass();
    $result->params = $signedparams;
    $result->url = $toolurl;

    return $result;
}

/**
 * This function builds the request that must be sent to the tool producer
 *
 * @param stdClass      $user the user
 * @param stdClass      $company the company
 * @param object        $instance       Basic LTI instance object
 * @param stdClass      $typeconfig     LTI tool configuration
 * @param object|null   $course         Course object
 * @param object|null   $offering         Course object
 * @param int|null      $typeid         Basic LTI tool ID
 * @param string        $messagetype    LTI Message Type for this launch
 * @param int           $foruserid      User targeted by this launch
 *
 * @return array                    Request details
 */
function lti_build_request(
    $user,
    $company,
    $instance,
    $type,
    $course,
    $offering,
    $messagetype = 'basic-lti-launch-request',
    $foruserid = 0
) {

    if (empty($instance->contentdtl_id)) {
        $instance->contentdtl_id = 0;
    }

    $role = lti_get_ims_role($user);

    if ($offering) {
        $requestparams = array(
            'user_id' => $user->user_id,
            'lis_person_sourcedid' => $user->user_id,
            'roles' => $role,
            'context_id' => $offering->offering_id,
            "context_label" => "Course",
            'context_title' => trim($offering->course_name),
        );
    } else {
        $requestparams = array(
            'user_id' => $user->user_id,
            'lis_person_sourcedid' => $user->user_id,
            'roles' => $role,
            'context_id' => "1",
            "context_label" => "Course",
            'context_title' => trim($course->course_name),
        );
    }
    if ($foruserid) {
        $requestparams['for_user_id'] = $foruserid;
    }
    if ($messagetype) {
        $requestparams['lti_message_type'] = $messagetype;
    }
    if (!empty($instance->content_name)) {
        $requestparams['resource_link_title'] = trim($instance->content_name);
    }
    if (!empty($instance->content_desc)) {
        $intro = str_replace("\n", "\r\n", $instance->content_desc);
        $requestparams['resource_link_description'] = $intro;
    }
    if (!empty($instance->contentdtl_id)) {
        $requestparams['resource_link_id'] = $instance->contentdtl_id;
    }
    $requestparams['context_type'] = 'CourseSection';

    // Send user's name and email data if appropriate.
    if ($type->lti_sendname == LTI_SETTING_ALWAYS || $type->lti_sendname == LTI_SETTING_DELEGATE) {
        $requestparams['lis_person_name_given'] = $user->first_name;
        $requestparams['lis_person_name_family'] = $user->last_name;
        $requestparams['lis_person_name_full'] = $user->first_name;
        $requestparams['ext_user_username'] = $user->username;
    }

    if ($type->lti_sendemailaddr == LTI_SETTING_ALWAYS || $type->lti_sendemailaddr == LTI_SETTING_DELEGATE) {
        $requestparams['lis_person_contact_email_primary'] = $user->email_id;
    }

    return $requestparams;
}

/**
 * Gets the IMS role string for the specified user and LTI course module.
 *
 * @param mixed    $user  User object or user id
 *
 * @return string A role string suitable for passing with an LTI launch
 */
function lti_get_ims_role($user)
{
    $roles = array();
    $lti = new LTI();
    $stmt = $lti->get_role_user($user->user_id);
    $count = $stmt->rowCount();
    if ($count > 0) {
        array_push($roles, 'Instructor');
        array_push($roles, 'urn:lti:sysrole:ims/lis/Administrator', 'urn:lti:instrole:ims/lis/Administrator');
    } else {
        array_push($roles, 'Learner');
    }
    return join(',', $roles);

}

/**
 * This function builds the standard parameters for an LTI message that must be sent to the tool producer
 *
 * @param stdClass  $instance       Basic LTI instance object
 * @param boolean   $ltiversion     LTI version to be used for tool messages
 * @param string    $messagetype    The request message type. Defaults to basic-lti-launch-request if empty.
 *
 * @return array                    Message parameters
 */
function lti_build_standard_message($user, $company, $instance, $ltiversion, $messagetype = 'basic-lti-launch-request')
{
    $requestparams = array();

    //Viewing content
    if ($instance) {
        $requestparams['resource_link_id'] = $instance->contentdtl_id;
    }

    $requestparams['launch_presentation_locale'] = current_language($user->user_id);

    // Make sure we let the tool know what LMS they are being called from.
    //Tip: Put ext_lms in config, and ask sir to finalize it. And read all 3 params from config
    $requestparams['ext_lms'] = LMS_NAME;
    $requestparams['tool_consumer_info_product_family_code'] = LMS_VERSION;
    $requestparams['tool_consumer_info_version'] = LMS_CODE;

    // Add oauth_callback to be compliant with the 1.0A spec.
    $requestparams['oauth_callback'] = 'about:blank';

    $requestparams['lti_version'] = $ltiversion;
    $requestparams['lti_message_type'] = $messagetype;

    //Company related changes
    $requestparams["tool_consumer_instance_guid"] = $company->company_code;
    $requestparams['tool_consumer_instance_name'] = $company->company_name;
    $requestparams['tool_consumer_instance_description'] = "";

    return $requestparams;
}

/**
 * This function builds the custom parameters
 *
 * @param object|null    $toolproxy      Tool proxy instance object
 * @param object    $tool           Tool instance object
 * @param object    $instance       Tool placement instance object
 * @param array     $params         LTI launch parameters
 * @param string    $customstr      Custom parameters defined for tool
 * @param string    $instructorcustomstr      Custom parameters defined for this placement
 * @param boolean   $islti2         True if an LTI 2 tool is being launched
 *
 * @return array                    Custom parameters
 */
function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $customstr, $instructorcustomstr)
{
    // Concatenate the custom parameters from the administrator and the instructor
    // Instructor parameters are only taken into consideration if the administrator
    // has given permission.
    $custom = array();
    if ($customstr) {
        $custom = lti_split_custom_parameters($toolproxy, $tool, $params, $customstr);
    }
    if ($instructorcustomstr) {
        $custom = array_merge(
            lti_split_custom_parameters(
                $toolproxy,
                $tool,
                $params,
                $instructorcustomstr,
            ),
            $custom
        );
    }

    return $custom;
}

/**
 * Splits the custom parameters field to the various parameters
 *
 * @param object    $toolproxy      Tool proxy instance object
 * @param object    $tool           Tool instance object
 * @param array     $params         LTI launch parameters
 * @param string    $customstr      String containing the parameters
 * @param boolean   $islti2         True if an LTI 2 tool is being launched
 *
 * @return array of custom parameters
 */
function lti_split_custom_parameters($toolproxy, $tool, $params, $customstr)
{
    $splitted = lti_split_parameters($customstr);
    $retval = array();
    foreach ($splitted as $key => $val) {
        $val = lti_parse_custom_parameter($toolproxy, $tool, $offering_id = 0, $contentdtl_id = 0, $params, $val);
        $key2 = lti_map_keyname($key);
        $retval['custom_' . $key2] = $val;
        if ($tool->ltiversion === LTI_VERSION_1P3 && $key != $key2) {
            $retval['custom_' . $key] = $val;
        }
    }
    return $retval;
}

/**
 * Splits the custom parameters
 *
 * @param string    $customstr      String containing the parameters
 *
 * @return array of custom parameters
 */
function lti_split_parameters($customstr)
{
    $customstr = str_replace("\r\n", "\n", $customstr);
    $customstr = str_replace("\n\r", "\n", $customstr);
    $customstr = str_replace("\r", "\n", $customstr);
    $lines = explode("\n", $customstr); // Or should this split on "/[\n;]/"?
    $retval = array();
    foreach ($lines as $line) {
        $pos = strpos($line, '=');
        if ($pos === false || $pos < 1) {
            continue;
        }
        $key = trim(substr($line, 0, $pos));
        $val = trim(substr($line, $pos + 1, strlen($line)));
        $retval[$key] = $val;
    }
    return $retval;
}

/**
 * Used for building the names of the different custom parameters
 *
 * @param string $key   Parameter name
 * @param bool $tolower Do we want to convert the key into lower case?
 * @return string       Processed name
 */
function lti_map_keyname($key, $tolower = true)
{
    if ($tolower) {
        $newkey = '';
        $key = strtolower(trim($key));
        foreach (str_split($key) as $ch) {
            if (($ch >= 'a' && $ch <= 'z') || ($ch >= '0' && $ch <= '9')) {
                $newkey .= $ch;
            } else {
                $newkey .= '_';
            }
        }
    } else {
        $newkey = $key;
    }
    return $newkey;
}

/**
 * Returns the code for the current language
 *
 * @category string
 * @return string
 */
function current_language($uid)
{
    $return = 'en';
    $lti = new LTI();
    $stmt = $lti->get_language_user($uid);
    if ($stmt->rowCount() > 0) {
        if ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $return = $result['lang_cd_mobile'];
        }
    }
    // Just in case this slipped in from somewhere by accident.
    //$return = str_replace('_utf8', '', $return);

    return $return;
}

/**
 * Return an array of key/values to add to the launch parameters.
 *
 * @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
 * @param string $courseid the course id.
 * @param object $user The user id.
 * @param string $typeid The tool lti type id.
 * @param string $modlti The id of the lti activity.
 *
 * The type is passed to check the configuration
 * and not return parameters for services not used.
 *
 * @return array of key/value pairs to add as launch parameters.
 */
function get_launch_parameters($messagetype, $user, $type, $modlti = null, $grade_items = null)
{
    $launchparameters = array();

    // Only inject parameters if the service is enabled for this tool.
    if (isset($type->ltiservice_gradesynchronization)) {
        if (
            $type->ltiservice_gradesynchronization == GRADEBOOKSERVICES_READ ||
            $type->ltiservice_gradesynchronization == GRADEBOOKSERVICES_FULL
        ) {
            $id = null;
            if (!is_null($modlti)) {
                $id = $grade_items->grade_items_id;
            }
            $launchparameters['gradebookservices_scope'] = implode(',', get_permitted_scopes($type));
            $launchparameters['lineitems_url'] = '$LineItems.url';
            if (!is_null($id)) {
                $launchparameters['lineitem_url'] = '$LineItem.url';
            }

        }
    }
    return $launchparameters;
}

/**
 * Get the scope(s) permitted for this service.
 *
 * @return array
 */
function get_permitted_scopes($type)
{
    $scopes = array();
    if (!empty($setting = $type->ltiservice_gradesynchronization)) {
        $scopes[] = SCOPE_GRADEBOOKSERVICES_LINEITEM_READ;
        $scopes[] = SCOPE_GRADEBOOKSERVICES_RESULT_READ;
        $scopes[] = SCOPE_GRADEBOOKSERVICES_SCORE;
        if ($setting == GRADEBOOKSERVICES_FULL) {
            $scopes[] = SCOPE_GRADEBOOKSERVICES_LINEITEM;
        }
    }

    return $scopes;

}

/**
 * Return the mapping for standard message types to JWT message_type claim.
 *
 * @return array
 */
function lti_get_jwt_message_type_mapping()
{
    return array(
        'basic-lti-launch-request' => 'LtiResourceLinkRequest',
        'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest',
        'LtiDeepLinkingResponse' => 'ContentItemSelection',
        'LtiSubmissionReviewRequest' => 'LtiSubmissionReviewRequest',
    );
}

/**
 * Return an array of key/claim mapping allowing LTI 1.1 custom parameters
 * to be transformed to LTI 1.3 claims.
 *
 * @return array Key/value pairs of params to claim mapping.
 */
function get_service_jwt_claim_mappings(): array
{
    return [
        'custom_gradebookservices_scope' => [
            'suffix' => 'ags',
            'group' => 'endpoint',
            'claim' => 'scope',
            'isarray' => true
        ],
        'custom_lineitems_url' => [
            'suffix' => 'ags',
            'group' => 'endpoint',
            'claim' => 'lineitems',
            'isarray' => false
        ],
        'custom_lineitem_url' => [
            'suffix' => 'ags',
            'group' => 'endpoint',
            'claim' => 'lineitem',
            'isarray' => false
        ],
        'custom_results_url' => [
            'suffix' => 'ags',
            'group' => 'endpoint',
            'claim' => 'results',
            'isarray' => false
        ],
        'custom_result_url' => [
            'suffix' => 'ags',
            'group' => 'endpoint',
            'claim' => 'result',
            'isarray' => false
        ],
        'custom_scores_url' => [
            'suffix' => 'ags',
            'group' => 'endpoint',
            'claim' => 'scores',
            'isarray' => false
        ],
        'custom_score_url' => [
            'suffix' => 'ags',
            'group' => 'endpoint',
            'claim' => 'score',
            'isarray' => false
        ]
    ];
}

/**
 * Return the mapping for standard message parameters to JWT claim.
 *
 * @return array
 */
function lti_get_jwt_claim_mapping()
{
    $mapping = [];
    $mapping = array_merge($mapping, get_service_jwt_claim_mappings());
    $mapping = array_merge($mapping, [
        'accept_copy_advice' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'accept_copy_advice',
            'isarray' => false,
            'type' => 'boolean'
        ],
        'accept_media_types' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'accept_media_types',
            'isarray' => true
        ],
        'accept_multiple' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'accept_multiple',
            'isarray' => false,
            'type' => 'boolean'
        ],
        'accept_presentation_document_targets' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'accept_presentation_document_targets',
            'isarray' => true
        ],
        'accept_types' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'accept_types',
            'isarray' => true
        ],
        'accept_unsigned' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'accept_unsigned',
            'isarray' => false,
            'type' => 'boolean'
        ],
        'auto_create' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'auto_create',
            'isarray' => false,
            'type' => 'boolean'
        ],
        'can_confirm' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'can_confirm',
            'isarray' => false,
            'type' => 'boolean'
        ],
        'content_item_return_url' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'deep_link_return_url',
            'isarray' => false
        ],
        'content_items' => [
            'suffix' => 'dl',
            'group' => '',
            'claim' => 'content_items',
            'isarray' => true
        ],
        'data' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'data',
            'isarray' => false
        ],
        'text' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'text',
            'isarray' => false
        ],
        'title' => [
            'suffix' => 'dl',
            'group' => 'deep_linking_settings',
            'claim' => 'title',
            'isarray' => false
        ],
        'lti_msg' => [
            'suffix' => 'dl',
            'group' => '',
            'claim' => 'msg',
            'isarray' => false
        ],
        'lti_log' => [
            'suffix' => 'dl',
            'group' => '',
            'claim' => 'log',
            'isarray' => false
        ],
        'lti_errormsg' => [
            'suffix' => 'dl',
            'group' => '',
            'claim' => 'errormsg',
            'isarray' => false
        ],
        'lti_errorlog' => [
            'suffix' => 'dl',
            'group' => '',
            'claim' => 'errorlog',
            'isarray' => false
        ],
        'context_id' => [
            'suffix' => '',
            'group' => 'context',
            'claim' => 'id',
            'isarray' => false
        ],
        'context_label' => [
            'suffix' => '',
            'group' => 'context',
            'claim' => 'label',
            'isarray' => false
        ],
        'context_title' => [
            'suffix' => '',
            'group' => 'context',
            'claim' => 'title',
            'isarray' => false
        ],
        'context_type' => [
            'suffix' => '',
            'group' => 'context',
            'claim' => 'type',
            'isarray' => true
        ],
        'for_user_id' => [
            'suffix' => '',
            'group' => 'for_user',
            'claim' => 'user_id',
            'isarray' => false
        ],
        'lis_course_offering_sourcedid' => [
            'suffix' => '',
            'group' => 'lis',
            'claim' => 'course_offering_sourcedid',
            'isarray' => false
        ],
        'lis_course_section_sourcedid' => [
            'suffix' => '',
            'group' => 'lis',
            'claim' => 'course_section_sourcedid',
            'isarray' => false
        ],
        'launch_presentation_css_url' => [
            'suffix' => '',
            'group' => 'launch_presentation',
            'claim' => 'css_url',
            'isarray' => false
        ],
        'launch_presentation_document_target' => [
            'suffix' => '',
            'group' => 'launch_presentation',
            'claim' => 'document_target',
            'isarray' => false
        ],
        'launch_presentation_height' => [
            'suffix' => '',
            'group' => 'launch_presentation',
            'claim' => 'height',
            'isarray' => false
        ],
        'launch_presentation_locale' => [
            'suffix' => '',
            'group' => 'launch_presentation',
            'claim' => 'locale',
            'isarray' => false
        ],
        'launch_presentation_return_url' => [
            'suffix' => '',
            'group' => 'launch_presentation',
            'claim' => 'return_url',
            'isarray' => false
        ],
        'launch_presentation_width' => [
            'suffix' => '',
            'group' => 'launch_presentation',
            'claim' => 'width',
            'isarray' => false
        ],
        'lis_person_contact_email_primary' => [
            'suffix' => '',
            'group' => null,
            'claim' => 'email',
            'isarray' => false
        ],
        'lis_person_name_family' => [
            'suffix' => '',
            'group' => null,
            'claim' => 'family_name',
            'isarray' => false
        ],
        'lis_person_name_full' => [
            'suffix' => '',
            'group' => null,
            'claim' => 'name',
            'isarray' => false
        ],
        'lis_person_name_given' => [
            'suffix' => '',
            'group' => null,
            'claim' => 'given_name',
            'isarray' => false
        ],
        'lis_person_sourcedid' => [
            'suffix' => '',
            'group' => 'lis',
            'claim' => 'person_sourcedid',
            'isarray' => false
        ],
        'user_id' => [
            'suffix' => '',
            'group' => null,
            'claim' => 'sub',
            'isarray' => false
        ],
        'user_image' => [
            'suffix' => '',
            'group' => null,
            'claim' => 'picture',
            'isarray' => false
        ],
        'roles' => [
            'suffix' => '',
            'group' => '',
            'claim' => 'roles',
            'isarray' => true
        ],
        'role_scope_mentor' => [
            'suffix' => '',
            'group' => '',
            'claim' => 'role_scope_mentor',
            'isarray' => false
        ],
        'deployment_id' => [
            'suffix' => '',
            'group' => '',
            'claim' => 'deployment_id',
            'isarray' => false
        ],
        'lti_message_type' => [
            'suffix' => '',
            'group' => '',
            'claim' => 'message_type',
            'isarray' => false
        ],
        'lti_version' => [
            'suffix' => '',
            'group' => '',
            'claim' => 'version',
            'isarray' => false
        ],
        'resource_link_description' => [
            'suffix' => '',
            'group' => 'resource_link',
            'claim' => 'description',
            'isarray' => false
        ],
        'resource_link_id' => [
            'suffix' => '',
            'group' => 'resource_link',
            'claim' => 'id',
            'isarray' => false
        ],
        'resource_link_title' => [
            'suffix' => '',
            'group' => 'resource_link',
            'claim' => 'title',
            'isarray' => false
        ],
        'tool_consumer_info_product_family_code' => [
            'suffix' => '',
            'group' => 'tool_platform',
            'claim' => 'product_family_code',
            'isarray' => false
        ],
        'tool_consumer_info_version' => [
            'suffix' => '',
            'group' => 'tool_platform',
            'claim' => 'version',
            'isarray' => false
        ],
        'tool_consumer_instance_contact_email' => [
            'suffix' => '',
            'group' => 'tool_platform',
            'claim' => 'contact_email',
            'isarray' => false
        ],
        'tool_consumer_instance_description' => [
            'suffix' => '',
            'group' => 'tool_platform',
            'claim' => 'description',
            'isarray' => false
        ],
        'tool_consumer_instance_guid' => [
            'suffix' => '',
            'group' => 'tool_platform',
            'claim' => 'guid',
            'isarray' => false
        ],
        'tool_consumer_instance_name' => [
            'suffix' => '',
            'group' => 'tool_platform',
            'claim' => 'name',
            'isarray' => false
        ],
        'tool_consumer_instance_url' => [
            'suffix' => '',
            'group' => 'tool_platform',
            'claim' => 'url',
            'isarray' => false
        ]
    ]);
    return $mapping;
}

/**
 * Converts the message paramters to their equivalent JWT claim and signs the payload to launch the external tool using JWT
 *
 * @param array  $parms        Parameters to be passed for signing
 * @param string $endpoint     url of the external tool
 * @param string $oauthconsumerkey
 * @param string $typeid       ID of LTI tool type
 * @param string $nonce        Nonce value to use
 * @return array|null
 */
function lti_sign_jwt($company, $parms, $endpoint, $oauthconsumerkey, $typeid = 0, $nonce = '')
{

    $contentdtl_id = $parms['resource_link_id'];
    $lti = new LTI();
    if ($contentdtl_id != null) {
        $lti_record = $lti->get_lti($contentdtl_id);
        if ($result = $lti_record->fetch(PDO::FETCH_ASSOC)) {
            $custom_id = $result['custom_id'];
        }
    }
    if (empty($typeid)) {
        $typeid = 0;
    }
    $messagetypemapping = lti_get_jwt_message_type_mapping();
    if (isset($parms['lti_message_type']) && array_key_exists($parms['lti_message_type'], $messagetypemapping)) {
        $parms['lti_message_type'] = $messagetypemapping[$parms['lti_message_type']];
    }
    if (isset($parms['roles'])) {
        $roles = explode(',', $parms['roles']);
        $newroles = array();
        foreach ($roles as $role) {
            if (strpos($role, 'urn:lti:role:ims/lis/') === 0) {
                $role = 'http://purl.imsglobal.org/vocab/lis/v2/membership#' . substr($role, 21);
            } else if (strpos($role, 'urn:lti:instrole:ims/lis/') === 0) {
                $role = 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#' . substr($role, 25);
            } else if (strpos($role, 'urn:lti:sysrole:ims/lis/') === 0) {
                $role = 'http://purl.imsglobal.org/vocab/lis/v2/system/person#' . substr($role, 24);
            } else if ((strpos($role, '://') === false) && (strpos($role, 'urn:') !== 0)) {
                $role = "http://purl.imsglobal.org/vocab/lis/v2/membership#{$role}";
            }
            $newroles[] = $role;
        }
        $parms['roles'] = implode(',', $newroles);
    }

    $now = time();
    if (empty($nonce)) {
        $nonce = bin2hex(openssl_random_pseudo_bytes(10));
    }
    $claimmapping = lti_get_jwt_claim_mapping();
    //$now-15 added because after changing current time to particular timezone adds plus one second extra
    $payload = array(
        'nonce' => $nonce,
        'iat' => $now-15,
        'exp' => $now + 60,
    );
    $payload['iss'] = CLIENT_HTTP_PATH;
    $payload['aud'] = $oauthconsumerkey;
    $payload[LTI_JWT_CLAIM_PREFIX . '/claim/deployment_id'] = (string) ($typeid);
    $payload[LTI_JWT_CLAIM_PREFIX . '/claim/target_link_uri'] = $endpoint;
    if ($contentdtl_id != null) {
        $payload["https://purl.imsglobal.org/spec/lti/claim/custom"]["id"] = $custom_id;
    }

    foreach ($parms as $key => $value) {
        $claim = LTI_JWT_CLAIM_PREFIX;
        if (array_key_exists($key, $claimmapping)) {
            $mapping = $claimmapping[$key];
            $type = $mapping["type"] ?? "string";
            if ($mapping['isarray']) {
                $value = explode(',', $value);
                sort($value);
            } else if ($type == 'boolean') {
                $value = isset($value) && ($value == 'true');
            }
            if (!empty($mapping['suffix'])) {
                $claim .= "-{$mapping['suffix']}";
            }
            $claim .= '/claim/';
            if (is_null($mapping['group'])) {
                $payload[$mapping['claim']] = $value;
            } else if (empty($mapping['group'])) {
                $payload["{$claim}{$mapping['claim']}"] = $value;
            } else {
                $claim .= $mapping['group'];
                $payload[$claim][$mapping['claim']] = $value;
            }
        } else if (strpos($key, 'custom_') === 0) {
            $payload["{$claim}/claim/custom"][substr($key, 7)] = $value;
        } else if (strpos($key, 'ext_') === 0) {
            $payload["{$claim}/claim/ext"][substr($key, 4)] = $value;
        }
    }
    $privatekey = $company->private_key;
    $kid = $company->kid;
    $jwt = JWT::encode($payload, $privatekey, 'RS256', $kid);
    $newparms = array();
    $newparms['id_token'] = $jwt;
    return $newparms;
}

/**
 * Posts the launch petition HTML
 *
 * @param array $newparms   Signed parameters
 * @param string $endpoint  URL of the external tool
 * @param bool $debug       Debug (true/false)
 * @return string
 */
//Tip: Check in next version
function lti_post_launch_html($newparms, $endpoint, $debug = false)
{
    $r = "<form action=\"" . $endpoint .
        "\" name=\"ltiLaunchForm\" id=\"ltiLaunchForm\" method=\"post\" encType=\"application/x-www-form-urlencoded\">\n";

    // Contruct html for the launch parameters.
    foreach ($newparms as $key => $value) {
        $key = htmlspecialchars($key, ENT_COMPAT);
        $value = htmlspecialchars($value, ENT_COMPAT);
        if ($key == "ext_submit") {
            $r .= "<input type=\"submit\"";
        } else {
            $r .= "<input type=\"hidden\" name=\"{$key}\"";
        }
        $r .= " value=\"";
        $r .= $value;
        $r .= "\"/>\n";
    }

    if ($debug) {
        $r .= "<script language=\"javascript\"> \n";
        $r .= "  //<![CDATA[ \n";
        $r .= "function basicltiDebugToggle() {\n";
        $r .= "    var ele = document.getElementById(\"basicltiDebug\");\n";
        $r .= "    if (ele.style.display == \"block\") {\n";
        $r .= "        ele.style.display = \"none\";\n";
        $r .= "    }\n";
        $r .= "    else {\n";
        $r .= "        ele.style.display = \"block\";\n";
        $r .= "    }\n";
        $r .= "} \n";
        $r .= "  //]]> \n";
        $r .= "</script>\n";
        $r .= "<a id=\"displayText\" href=\"javascript:basicltiDebugToggle();\">";
        $r .= get_string("toggle_debug_data", "lti") . "</a>\n";
        $r .= "<div id=\"basicltiDebug\" style=\"display:none\">\n";
        $r .= "<b>" . get_string("basiclti_endpoint", "lti") . "</b><br/>\n";
        $r .= $endpoint . "<br/>\n&nbsp;<br/>\n";
        $r .= "<b>" . get_string("basiclti_parameters", "lti") . "</b><br/>\n";
        foreach ($newparms as $key => $value) {
            $key = htmlspecialchars($key, ENT_COMPAT);
            $value = htmlspecialchars($value, ENT_COMPAT);
            $r .= "$key = $value<br/>\n";
        }
        $r .= "&nbsp;<br/>\n";
        $r .= "</div>\n";
    }
    $r .= "</form>\n";

    // Auto-submit the form if endpoint is set.
    if ($endpoint !== '' && !$debug) {
        $r .= " <script type=\"text/javascript\"> \n" .
            "  //<![CDATA[ \n" .
            "    document.ltiLaunchForm.submit(); \n" .
            "  //]]> \n" .
            " </script> \n";
    }
    return $r;
}

/**
 * Return the launch data required for opening the external tool.
 *
 * @param  stdClass $user the user
 * @param  stdClass $company the company
 * @param  stdClass $instance the external tool activity settings
 * @param  stdClass $tool the external tool activity settings
 * @param  string $nonce  the nonce value to use (applies to LTI 1.3 only)
 * @return array the endpoint URL and parameters (including the signature)
 * @since  Moodle 3.0
 */
function lti_get_launch_data($user, $company, $offering_details, $contentdtl_id, $instance, $grade_items, $tool, $nonce = '', $messagetype = 'basic-lti-launch-request', $foruserid = 0)
{
    $messagetype = $messagetype ? $messagetype : 'basic-lti-launch-request';

    $typeid = $tool->typeid;
    $ltiversion = $tool->lti_ltiversion;

    $toolproxy = null;
    if (!empty($instance->resourcekey)) {
        $key = $instance->resourcekey;
    } else if ($ltiversion === LTI_VERSION_1P3) {
        $key = $tool->lti_clientid;
    } else if (!empty($tool->resourcekey)) {
        $key = $tool->resourcekey;
    } else {
        $key = '';
    }
    if (!empty($instance->password)) {
        $secret = $instance->password;
    } else if (!empty($tool->password)) {
        $secret = $tool->password;
    } else {
        $secret = '';
    }

    $endpoint = !empty($instance->tool_url) ? $instance->tool_url : $tool->lti_toolurl;
    $endpoint = trim($endpoint);

    //Tip: not implemented secure tool url, next version
    // // If the current request is using SSL and a secure tool URL is specified, use it.
    // if (lti_request_is_using_ssl() && !empty($instance->securetoolurl)) {
    //     $endpoint = trim($instance->securetoolurl);
    // }

    // Tip: check http or https, next version
    // If SSL is forced, use the secure tool url if specified. Otherwise, make sure https is on the normal launch URL.
    // if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) {
    //     if (!empty($instance->securetoolurl)) {
    //         $endpoint = trim($instance->securetoolurl);
    //     }

    //     if ($endpoint !== '') {
    //         $endpoint = lti_ensure_url_is_https($endpoint);
    //     }
    // } else if ($endpoint !== '' && !strstr($endpoint, '://')) {
    //     $endpoint = 'http://' . $endpoint;
    // }

    $allparams = lti_build_request($user, $company, $instance, $tool, null, $offering_details, $messagetype, $foruserid);

    $requestparams = $allparams;

    $requestparams = array_merge($requestparams, lti_build_standard_message($user, $company, $instance, $ltiversion, $messagetype));
    $customstr = '';
    if (isset($tool->lti_customparameters)) {
        $customstr = $tool->lti_customparameters;
    }

    [$endpoint, $customstr] = override_endpoint(
        $messagetype,
        $endpoint,
        $customstr,
        $instance->course_id,
        $instance
    );

    $requestparams = array_merge(
        $requestparams,
        lti_build_custom_parameters(
            $toolproxy,
            $tool,
            $instance,
            $allparams,
            $customstr,
            $instance->instructorcustomparameters
        )
    );

    $launchcontainer = lti_get_launch_container($instance, $tool);
    //Tip:offering / course id pass and make retuern url, and start handling return url, next version
    // $returnurlparams = array(
    //     'course' => $course->id,
    //     'launch_container' => $launchcontainer,
    //     'instanceid' => $instance->id,
    //     'sesskey' => sesskey()
    // );
    // $query = http_build_query(array('params' => $returnurlparams));
    // // Add the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
    // $url = '/mod/lti/return.php?'.$query;
    // $returnurl = $url->out(false);
    $returnurl = "";

    $target = '';
    switch ($launchcontainer) {
        case LTI_LAUNCH_CONTAINER_EMBED:
        case LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS:
            $target = 'iframe';
            break;
        case LTI_LAUNCH_CONTAINER_REPLACE_WINDOW:
            $target = 'frame';
            break;
        case LTI_LAUNCH_CONTAINER_WINDOW:
            $target = 'window';
            break;
    }
    if (!empty($target)) {
        $requestparams['launch_presentation_document_target'] = $target;
    }

    $requestparams['launch_presentation_return_url'] = $returnurl;

    // Add the parameters configured by the LTI services.

    $serviceparameters = get_launch_parameters(
        'basic-lti-launch-request',
        $user->user_id,
        $tool,
        $instance->lti_id,
        $grade_items
    );
    foreach ($serviceparameters as $paramkey => $paramvalue) {
        $requestparams['custom_' . $paramkey] = lti_parse_custom_parameter(
            $toolproxy,
            $tool,
            $offering_details->offering_id,
            $contentdtl_id,
            $requestparams,
            $paramvalue
        );
    }

    //Tip: not implemented ltisource plugin, next version
    // Allow request params to be updated by sub-plugins.
    // $plugins = core_component::get_plugin_list('ltisource');
    // foreach (array_keys($plugins) as $plugin) {
    //     $pluginparams = component_callback(
    //         'ltisource_' . $plugin,
    //         'before_launch',
    //         array($instance, $endpoint, $requestparams),
    //         array()
    //     );
    //     if (!empty($pluginparams) && is_array($pluginparams)) {
    //         $requestparams = array_merge($requestparams, $pluginparams);
    //     }
    // }

    if ($ltiversion === LTI_VERSION_1P3) {

        $parms = lti_sign_jwt($company, $requestparams, $endpoint, $key, $typeid, $nonce);
        $endpointurl = parse_url($endpoint, PHP_URL_QUERY);
        parse_str($endpointurl, $query_params);
        // Strip querystring params in endpoint url from $parms to avoid duplication.
        if (!empty($query_params) && !empty($parms)) {
            foreach (array_keys($query_params) as $paramname) {
                if (isset($parms[$paramname])) {
                    unset($parms[$paramname]);
                }
            }
        }

    } else {
        // If no key and secret, do the launch unsigned.
        $returnurlparams['unsigned'] = '1';
        $parms = $requestparams;
    }

    return array($endpoint, $parms);
}


function lti_get_launch_container($lti, $toolconfig)
{
    if (empty($lti->launchcontainer)) {
        $lti->launchcontainer = LTI_LAUNCH_CONTAINER_DEFAULT;
    }

    if ($lti->launchcontainer == LTI_LAUNCH_CONTAINER_DEFAULT) {
        if (isset($toolconfig->launchcontainer)) {
            $launchcontainer = $toolconfig->launchcontainer;
        }
    } else {
        $launchcontainer = $lti->launchcontainer;
    }

    if (empty($launchcontainer) || $launchcontainer == LTI_LAUNCH_CONTAINER_DEFAULT) {
        $launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
    }

    return $launchcontainer;
}

function lti_ensure_url_is_https($url)
{
    if (!strstr($url, '://')) {
        $url = 'https://' . $url;
    } else {
        // If the URL starts with http, replace with https.
        if (stripos($url, 'http://') === 0) {
            $url = 'https://' . substr($url, 7);
        }
    }

    return $url;
}

/**
 * For submission review, if there is a dedicated URL, use it as the target link.
 *
 * @param string $messagetype message type for this launch
 * @param string $targetlinkuri current target link uri
 * @param string|null $customstr concatenated list of custom parameters
 * @param int $courseid
 * @param null|object $lti LTI Instance.
 *
 * @return array containing the target link URL and the custom params string to use.
 */
function override_endpoint(
    string $messagetype,
    string $targetlinkuri,
    ?string $customstr,
    int $courseid,
    ?object $lti = null
): array {
    global $DB;
    if ($messagetype == 'LtiSubmissionReviewRequest' && isset($lti->lti_id)) {
        $conditions = array('courseid' => $courseid, 'ltilinkid' => $lti->lti_id);
        $coupledlineitems = $DB->get_records('ltiservice_gradebookservices', $conditions);
        if (count($coupledlineitems) == 1) {
            $item = reset($coupledlineitems);
            $url = $item->subreviewurl;
            $subreviewparams = $item->subreviewparams;
            if (!empty($url) && $url != 'DEFAULT') {
                $targetlinkuri = $url;
            }
            if (!empty($subreviewparams)) {
                if (!empty($customstr)) {
                    $customstr .= "\n{$subreviewparams}";
                } else {
                    $customstr = $subreviewparams;
                }
            }
        }
    }
    return [$targetlinkuri, $customstr];
}

/**
 * Parse a custom parameter to replace any substitution variables
 *
 * @param object|null    $toolproxy      Tool proxy instance object
 * @param object    $tool           Tool instance object
 * @param array     $params         LTI launch parameters
 * @param string    $value          Custom parameter value
 * @param boolean   $islti2         True if an LTI 2 tool is being launched
 *
 * @return string Parsed value of custom parameter
 */
function lti_parse_custom_parameter($toolproxy, $tool, $offering_id, $contentdtl_id, $params, $value)
{
    // This is required as {${$valarr[0]}->{$valarr[1]}}" may be using the USER or COURSE var.

    if ($value) {
        if (substr($value, 0, 1) == '\\') {
            $value = substr($value, 1);
        } else if (substr($value, 0, 1) == '$') {
            $value1 = substr($value, 1);
            //Tip: Commented for now, no need to implement
            //$enabledcapabilities = lti_get_enabled_capabilities($tool);

            $capabilities = lti_get_capabilities();
            if (array_key_exists($value1, $capabilities)) {
                $val = $capabilities[$value1];
                if ($val) {
                    if (substr($val, 0, 1) != '$') {
                        $value = $params[$val];
                    } else {
                        $valarr = explode('->', substr($val, 1), 2);
                        $value = "{${$valarr[0]}->{$valarr[1]}}";
                        $value = str_replace('<br />', ' ', $value);
                        $value = str_replace('<br>', ' ', $value);
                        //Tip: Next version, if any sign characters, then format the string like str_replace(array('<', '>'), array('&lt;', '&gt;'), strip_tags($string));
                        //$value = format_string($value);
                    }
                }
            } else {
                $val = $value;
                $services[] = new gradebookservices();
                foreach ($services as $service) {
                    $service->set_tool_proxy($toolproxy);
                    $service->set_type($tool);
                    $value = $service->parse_value($val, $offering_id, $contentdtl_id);
                    if ($val != $value) {
                        break;
                    }
                }
            }

        }
    }
    return $value;
}

/**
 * Initializes an array with the capabilities supported by the LTI module
 *
 * @return array List of capability names (without a dollar sign prefix)
 */
function lti_get_capabilities()
{

    $capabilities = array(
        'basic-lti-launch-request' => '',
        'ContentItemSelectionRequest' => '',
        'ToolProxyRegistrationRequest' => '',
        'Context.id' => 'context_id',
        'Context.title' => 'context_title',
        'Context.label' => 'context_label',
        'Context.id.history' => null,
        'Context.sourcedId' => 'lis_course_section_sourcedid',
        'Context.longDescription' => '$COURSE->summary',
        'Context.timeFrame.begin' => '$COURSE->startdate',
        'CourseSection.title' => 'context_title',
        'CourseSection.label' => 'context_label',
        'CourseSection.sourcedId' => 'lis_course_section_sourcedid',
        'CourseSection.longDescription' => '$COURSE->summary',
        'CourseSection.timeFrame.begin' => null,
        'CourseSection.timeFrame.end' => null,
        'ResourceLink.id' => 'resource_link_id',
        'ResourceLink.title' => 'resource_link_title',
        'ResourceLink.description' => 'resource_link_description',
        'User.id' => 'user_id',
        'User.username' => '$USER->username',
        'Person.name.full' => 'lis_person_name_full',
        'Person.name.given' => 'lis_person_name_given',
        'Person.name.family' => 'lis_person_name_family',
        'Person.email.primary' => 'lis_person_contact_email_primary',
        'Person.sourcedId' => 'lis_person_sourcedid',
        'Person.name.middle' => '$USER->middlename',
        'Person.address.street1' => '$USER->address',
        'Person.address.locality' => '$USER->city',
        'Person.address.country' => '$USER->country',
        'Person.address.timezone' => '$USER->timezone',
        'Person.phone.primary' => '$USER->phone1',
        'Person.phone.mobile' => '$USER->phone2',
        'Person.webaddress' => '$USER->url',
        'Membership.role' => 'roles',
        'Result.sourcedId' => 'lis_result_sourcedid',
        'Result.autocreate' => 'lis_outcome_service_url',
        'BasicOutcome.sourcedId' => 'lis_result_sourcedid',
        'BasicOutcome.url' => 'lis_outcome_service_url',
        'Moodle.Person.userGroupIds' => null
    );

    return $capabilities;

}

/**
 * Extracts the enabled capabilities into an array, including those implicitly declared in a parameter
 *
 * @param object $tool  Tool instance object
 *
 * @return array List of enabled capabilities
 */
function lti_get_enabled_capabilities($tool)
{
    if (!isset($tool)) {
        return array();
    }
    if (!empty($tool->enabledcapability)) {
        $enabledcapabilities = explode("\n", $tool->enabledcapability);
    } else {
        $enabledcapabilities = array();
    }
    if (!empty($tool->parameter)) {
        $paramstr = str_replace("\r\n", "\n", $tool->parameter);
        $paramstr = str_replace("\n\r", "\n", $paramstr);
        $paramstr = str_replace("\r", "\n", $paramstr);
        $params = explode("\n", $paramstr);
        foreach ($params as $param) {
            $pos = strpos($param, '=');
            if (($pos === false) || ($pos < 1)) {
                continue;
            }
            $value = trim(substr($param, $pos + 1, strlen($param)));
            if (substr($value, 0, 1) == '$') {
                $value = substr($value, 1);
                if (!in_array($value, $enabledcapabilities)) {
                    $enabledcapabilities[] = $value;
                }
            }
        }
    }
    return $enabledcapabilities;
}

/**
 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
 * sesskey string if $USER exists, or boolean false if not.
 *
 * @uses $USER
 * @return string
 */
//Tip: Next version
/*function sesskey()
{
    // note: do not use $USER because it may not be initialised yet
    if (empty($_SESSION['uid']->sesskey)) {
        if (!isset($_SESSION['uid'])) {
            // This should never happen,
            // do not mess with session and globals here,
            // let any checks fail instead!
            return false;
        }
        $_SESSION['uid']->sesskey = random_string(10);
    }

    return $_SESSION['uid']->sesskey;
}*/

/**
 * Generate and return a random string of the specified length.
 *
 * @param int $length The length of the string to be created.
 * @return string
 */
function random_string($length = 15)
{
    $randombytes = random_bytes_emulate($length);
    $pool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $pool .= 'abcdefghijklmnopqrstuvwxyz';
    $pool .= '0123456789';
    $poollen = strlen($pool);
    $string = '';
    for ($i = 0; $i < $length; $i++) {
        $rand = ord($randombytes[$i]);
        $string .= substr($pool, ($rand % ($poollen)), 1);
    }
    return $string;
}

/**
 * Try to generates cryptographically secure pseudo-random bytes.
 *
 * Note this is achieved by fallbacking between:
 *  - PHP 7 random_bytes().
 *  - OpenSSL openssl_random_pseudo_bytes().
 *  - In house random generator getting its entropy from various, hard to guess, pseudo-random sources.
 *
 * @param int $length requested length in bytes
 * @return string binary data
 */
function random_bytes_emulate($length)
{
    if ($length <= 0) {
        return '';
    }
    if (function_exists('random_bytes')) {
        // Use PHP 7 goodness.
        $hash = @random_bytes($length);
        if ($hash !== false) {
            return $hash;
        }
    }
    if (function_exists('openssl_random_pseudo_bytes')) {
        // If you have the openssl extension enabled.
        $hash = openssl_random_pseudo_bytes($length);
        if ($hash !== false) {
            return $hash;
        }
    }
    return substr($hash, 0, $length);
}

function params(array $params = null)
{
    $params = (array) $params;

    foreach ($params as $key => $value) {
        if (is_int($key)) {
            echo 'Url parameters can not have numeric keys!';
        }
        if (!is_string($value)) {
            if (is_array($value)) {
                echo 'Url parameters values can not be arrays!';
            }
            if (is_object($value) and !method_exists($value, '__toString')) {
                echo 'Url parameters values can not be objects, unless __toString() is defined!';
            }
        }
        $params[$key] = (string) $value;
    }
    return $params;
}