__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

www-data@216.73.216.10: ~ $
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
//
// This file is part of BasicLTI4Moodle
//
// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
// are already supporting or going to support BasicLTI. This project Implements the consumer
// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
// at the GESSI research group at UPC.
// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
//
// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
// of the Universitat Politecnica de Catalunya http://www.upc.edu
// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.

/**
 * This file contains the library of functions and constants for the lti module
 *
 * @package mod_lti
 * @copyright  2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
 *  marc.alier@upc.edu
 * @copyright  2009 Universitat Politecnica de Catalunya http://www.upc.edu
 * @author     Marc Alier
 * @author     Jordi Piguillem
 * @author     Nikolas Galanis
 * @author     Chris Scribner
 * @copyright  2015 Vital Source Technologies http://vitalsource.com
 * @author     Stephen Vickers
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die;

// TODO: Switch to core oauthlib once implemented - MDL-30149.
use mod_lti\helper;
use moodle\mod\lti as lti;
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Firebase\JWT\Key;
use mod_lti\local\ltiopenid\jwks_helper;
use mod_lti\local\ltiopenid\registration_helper;

global $CFG;
require_once($CFG->dirroot.'/mod/lti/OAuth.php');
require_once($CFG->libdir.'/weblib.php');
require_once($CFG->dirroot . '/course/modlib.php');
require_once($CFG->dirroot . '/mod/lti/TrivialStore.php');

define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i');

define('LTI_LAUNCH_CONTAINER_DEFAULT', 1);
define('LTI_LAUNCH_CONTAINER_EMBED', 2);
define('LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS', 3);
define('LTI_LAUNCH_CONTAINER_WINDOW', 4);
define('LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW', 5);

define('LTI_TOOL_STATE_ANY', 0);
define('LTI_TOOL_STATE_CONFIGURED', 1);
define('LTI_TOOL_STATE_PENDING', 2);
define('LTI_TOOL_STATE_REJECTED', 3);
define('LTI_TOOL_PROXY_TAB', 4);

define('LTI_TOOL_PROXY_STATE_CONFIGURED', 1);
define('LTI_TOOL_PROXY_STATE_PENDING', 2);
define('LTI_TOOL_PROXY_STATE_ACCEPTED', 3);
define('LTI_TOOL_PROXY_STATE_REJECTED', 4);

define('LTI_SETTING_NEVER', 0);
define('LTI_SETTING_ALWAYS', 1);
define('LTI_SETTING_DELEGATE', 2);

define('LTI_COURSEVISIBLE_NO', 0);
define('LTI_COURSEVISIBLE_PRECONFIGURED', 1);
define('LTI_COURSEVISIBLE_ACTIVITYCHOOSER', 2);

define('LTI_VERSION_1', 'LTI-1p0');
define('LTI_VERSION_2', 'LTI-2p0');
define('LTI_VERSION_1P3', '1.3.0');
define('LTI_RSA_KEY', 'RSA_KEY');
define('LTI_JWK_KEYSET', 'JWK_KEYSET');

define('LTI_DEFAULT_ORGID_SITEID', 'SITEID');
define('LTI_DEFAULT_ORGID_SITEHOST', 'SITEHOST');

define('LTI_ACCESS_TOKEN_LIFE', 3600);

// Standard prefix for JWT claims.
define('LTI_JWT_CLAIM_PREFIX', 'https://purl.imsglobal.org/spec/lti');

/**
 * 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 the mapping for standard message parameters to JWT claim.
 *
 * @return array
 */
function lti_get_jwt_claim_mapping() {
    $mapping = [];
    $services = lti_get_services();
    foreach ($services as $service) {
        $mapping = array_merge($mapping, $service->get_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;
}

/**
 * Return the type of the instance, using domain matching if no explicit type is set.
 *
 * @param  object $instance the external tool activity settings
 * @return object|null
 * @since  Moodle 3.9
 */
function lti_get_instance_type(object $instance): ?object {
    if (empty($instance->typeid)) {
        if (!$tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course)) {
            $tool = lti_get_tool_by_url_match($instance->securetoolurl,  $instance->course);
        }
        return $tool;
    }
    return lti_get_type($instance->typeid);
}

/**
 * Return the launch data required for opening the external tool.
 *
 * @param  stdClass $instance 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($instance, $nonce = '', $messagetype = 'basic-lti-launch-request', $foruserid = 0) {
    global $PAGE, $USER;
    $messagetype = $messagetype ? $messagetype : 'basic-lti-launch-request';
    $tool = lti_get_instance_type($instance);
    if ($tool) {
        $typeid = $tool->id;
        $ltiversion = $tool->ltiversion;
    } else {
        $typeid = null;
        $ltiversion = LTI_VERSION_1;
    }

    if ($typeid) {
        $typeconfig = lti_get_type_config($typeid);
    } else {
        // There is no admin configuration for this tool. Use configuration in the lti instance record plus some defaults.
        $typeconfig = (array)$instance;

        $typeconfig['sendname'] = $instance->instructorchoicesendname;
        $typeconfig['sendemailaddr'] = $instance->instructorchoicesendemailaddr;
        $typeconfig['customparameters'] = $instance->instructorcustomparameters;
        $typeconfig['acceptgrades'] = $instance->instructorchoiceacceptgrades;
        $typeconfig['allowroster'] = $instance->instructorchoiceallowroster;
        $typeconfig['forcessl'] = '0';
    }

    if (isset($tool->toolproxyid)) {
        $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
        $key = $toolproxy->guid;
        $secret = $toolproxy->secret;
    } else {
        $toolproxy = null;
        if (!empty($instance->resourcekey)) {
            $key = $instance->resourcekey;
        } else if ($ltiversion === LTI_VERSION_1P3) {
            $key = $tool->clientid;
        } else if (!empty($typeconfig['resourcekey'])) {
            $key = $typeconfig['resourcekey'];
        } else {
            $key = '';
        }
        if (!empty($instance->password)) {
            $secret = $instance->password;
        } else if (!empty($typeconfig['password'])) {
            $secret = $typeconfig['password'];
        } else {
            $secret = '';
        }
    }

    $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $typeconfig['toolurl'];
    $endpoint = trim($endpoint);

    // 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);
    }

    // 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;
    }

    $orgid = lti_get_organizationid($typeconfig);

    $course = $PAGE->course;
    $islti2 = isset($tool->toolproxyid);
    $allparams = lti_build_request($instance, $typeconfig, $course, $typeid, $islti2, $messagetype, $foruserid);
    if ($islti2) {
        $requestparams = lti_build_request_lti2($tool, $allparams);
    } else {
        $requestparams = $allparams;
    }
    $requestparams = array_merge($requestparams, lti_build_standard_message($instance, $orgid, $ltiversion, $messagetype));
    $customstr = '';
    if (isset($typeconfig['customparameters'])) {
        $customstr = $typeconfig['customparameters'];
    }
    $services = lti_get_services();
    foreach ($services as $service) {
        [$endpoint, $customstr] = $service->override_endpoint($messagetype,
            $endpoint, $customstr, $instance->course, $instance);
    }
    $requestparams = array_merge($requestparams, lti_build_custom_parameters($toolproxy, $tool, $instance, $allparams, $customstr,
        $instance->instructorcustomparameters, $islti2));

    $launchcontainer = lti_get_launch_container($instance, $typeconfig);
    $returnurlparams = array('course' => $course->id,
        'launch_container' => $launchcontainer,
        'instanceid' => $instance->id,
        'sesskey' => sesskey());

    // Add the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
    $url = new \moodle_url('/mod/lti/return.php', $returnurlparams);
    $returnurl = $url->out(false);

    if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) {
        $returnurl = lti_ensure_url_is_https($returnurl);
    }

    $target = '';
    switch($launchcontainer) {
        case LTI_LAUNCH_CONTAINER_EMBED:
        case LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS:
            $target = 'iframe';
            break;
        case LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_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.
    if ($typeid && !$islti2) {
        $services = lti_get_services();
        foreach ($services as $service) {
            $serviceparameters = $service->get_launch_parameters('basic-lti-launch-request',
                    $course->id, $USER->id , $typeid, $instance->id);
            foreach ($serviceparameters as $paramkey => $paramvalue) {
                $requestparams['custom_' . $paramkey] = lti_parse_custom_parameter($toolproxy, $tool, $requestparams, $paramvalue,
                    $islti2);
            }
        }
    }

    // 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 ((!empty($key) && !empty($secret)) || ($ltiversion === LTI_VERSION_1P3)) {
        if ($ltiversion !== LTI_VERSION_1P3) {
            $parms = lti_sign_parameters($requestparams, $endpoint, 'POST', $key, $secret);
        } else {
            $parms = lti_sign_jwt($requestparams, $endpoint, $key, $typeid, $nonce);
        }

        $endpointurl = new \moodle_url($endpoint);
        $endpointparams = $endpointurl->params();

        // Strip querystring params in endpoint url from $parms to avoid duplication.
        if (!empty($endpointparams) && !empty($parms)) {
            foreach (array_keys($endpointparams) 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);
}

/**
 * Launch an external tool activity.
 *
 * @param stdClass $instance the external tool activity settings
 * @param int $foruserid for user param, optional
 * @return string The HTML code containing the javascript code for the launch
 */
function lti_launch_tool($instance, $foruserid=0) {

    list($endpoint, $parms) = lti_get_launch_data($instance, '', '', $foruserid);
    $debuglaunch = ( $instance->debuglaunch == 1 );

    $content = lti_post_launch_html($parms, $endpoint, $debuglaunch);

    echo $content;
}

/**
 * Prepares an LTI registration request message
 *
 * @param object $toolproxy  Tool Proxy instance object
 */
function lti_register($toolproxy) {
    $endpoint = $toolproxy->regurl;

    // Change the status to pending.
    $toolproxy->state = LTI_TOOL_PROXY_STATE_PENDING;
    lti_update_tool_proxy($toolproxy);

    $requestparams = lti_build_registration_request($toolproxy);

    $content = lti_post_launch_html($requestparams, $endpoint, false);

    echo $content;
}


/**
 * Gets the parameters for the regirstration request
 *
 * @param object $toolproxy Tool Proxy instance object
 * @return array Registration request parameters
 */
function lti_build_registration_request($toolproxy) {
    $key = $toolproxy->guid;
    $secret = $toolproxy->secret;

    $requestparams = array();
    $requestparams['lti_message_type'] = 'ToolProxyRegistrationRequest';
    $requestparams['lti_version'] = 'LTI-2p0';
    $requestparams['reg_key'] = $key;
    $requestparams['reg_password'] = $secret;
    $requestparams['reg_url'] = $toolproxy->regurl;

    // Add the profile URL.
    $profileservice = lti_get_service_by_name('profile');
    $profileservice->set_tool_proxy($toolproxy);
    $requestparams['tc_profile_url'] = $profileservice->parse_value('$ToolConsumerProfile.url');

    // Add the return URL.
    $returnurlparams = array('id' => $toolproxy->id, 'sesskey' => sesskey());
    $url = new \moodle_url('/mod/lti/externalregistrationreturn.php', $returnurlparams);
    $returnurl = $url->out(false);

    $requestparams['launch_presentation_return_url'] = $returnurl;

    return $requestparams;
}


/** get Organization ID using default if no value provided
 * @param object $typeconfig
 * @return string
 */
function lti_get_organizationid($typeconfig) {
    global $CFG;
    // Default the organizationid if not specified.
    if (empty($typeconfig['organizationid'])) {
        if (($typeconfig['organizationid_default'] ?? LTI_DEFAULT_ORGID_SITEHOST) == LTI_DEFAULT_ORGID_SITEHOST) {
            $urlparts = parse_url($CFG->wwwroot);
            return $urlparts['host'];
        } else {
            return md5(get_site_identifier());
        }
    }
    return $typeconfig['organizationid'];
}

/**
 * Build source ID
 *
 * @param int $instanceid
 * @param int $userid
 * @param string $servicesalt
 * @param null|int $typeid
 * @param null|int $launchid
 * @return stdClass
 */
function lti_build_sourcedid($instanceid, $userid, $servicesalt, $typeid = null, $launchid = null) {
    $data = new \stdClass();

    $data->instanceid = $instanceid;
    $data->userid = $userid;
    $data->typeid = $typeid;
    if (!empty($launchid)) {
        $data->launchid = $launchid;
    } else {
        $data->launchid = mt_rand();
    }

    $json = json_encode($data);

    $hash = hash('sha256', $json . $servicesalt, false);

    $container = new \stdClass();
    $container->data = $data;
    $container->hash = $hash;

    return $container;
}

/**
 * This function builds the request that must be sent to the tool producer
 *
 * @param object    $instance       Basic LTI instance object
 * @param array     $typeconfig     Basic LTI tool configuration
 * @param object    $course         Course object
 * @param int|null  $typeid         Basic LTI tool ID
 * @param boolean   $islti2         True if an LTI 2 tool is being launched
 * @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($instance, $typeconfig, $course, $typeid = null, $islti2 = false,
    $messagetype = 'basic-lti-launch-request', $foruserid = 0) {
    global $USER, $CFG;

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

    $role = lti_get_ims_role($USER, $instance->cmid, $instance->course, $islti2);

    $requestparams = array(
        'user_id' => $USER->id,
        'lis_person_sourcedid' => $USER->idnumber,
        'roles' => $role,
        'context_id' => $course->id,
        'context_label' => trim(html_to_text($course->shortname, 0)),
        'context_title' => trim(html_to_text($course->fullname, 0)),
    );
    if ($foruserid) {
        $requestparams['for_user_id'] = $foruserid;
    }
    if ($messagetype) {
        $requestparams['lti_message_type'] = $messagetype;
    }
    if (!empty($instance->name)) {
        $requestparams['resource_link_title'] = trim(html_to_text($instance->name, 0));
    }
    if (!empty($instance->cmid)) {
        $intro = format_module_intro('lti', $instance, $instance->cmid);
        $intro = trim(html_to_text($intro, 0, false));

        // This may look weird, but this is required for new lines
        // so we generate the same OAuth signature as the tool provider.
        $intro = str_replace("\n", "\r\n", $intro);
        $requestparams['resource_link_description'] = $intro;
    }
    if (!empty($instance->id)) {
        $requestparams['resource_link_id'] = $instance->id;
    }
    if (!empty($instance->resource_link_id)) {
        $requestparams['resource_link_id'] = $instance->resource_link_id;
    }
    if ($course->format == 'site') {
        $requestparams['context_type'] = 'Group';
    } else {
        $requestparams['context_type'] = 'CourseSection';
        $requestparams['lis_course_section_sourcedid'] = $course->idnumber;
    }

    if (!empty($instance->id) && !empty($instance->servicesalt) && ($islti2 ||
            $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS ||
            ($typeconfig['acceptgrades'] == LTI_SETTING_DELEGATE && $instance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS))
    ) {
        $placementsecret = $instance->servicesalt;
        $sourcedid = json_encode(lti_build_sourcedid($instance->id, $USER->id, $placementsecret, $typeid));
        $requestparams['lis_result_sourcedid'] = $sourcedid;

        // Add outcome service URL.
        $serviceurl = new \moodle_url('/mod/lti/service.php');
        $serviceurl = $serviceurl->out();

        $forcessl = false;
        if (!empty($CFG->mod_lti_forcessl)) {
            $forcessl = true;
        }

        if ((isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) or $forcessl) {
            $serviceurl = lti_ensure_url_is_https($serviceurl);
        }

        $requestparams['lis_outcome_service_url'] = $serviceurl;
    }

    // Send user's name and email data if appropriate.
    if ($islti2 || $typeconfig['sendname'] == LTI_SETTING_ALWAYS ||
        ($typeconfig['sendname'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendname)
            && $instance->instructorchoicesendname == LTI_SETTING_ALWAYS)
    ) {
        $requestparams['lis_person_name_given'] = $USER->firstname;
        $requestparams['lis_person_name_family'] = $USER->lastname;
        $requestparams['lis_person_name_full'] = fullname($USER);
        $requestparams['ext_user_username'] = $USER->username;
    }

    if ($islti2 || $typeconfig['sendemailaddr'] == LTI_SETTING_ALWAYS ||
        ($typeconfig['sendemailaddr'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendemailaddr)
            && $instance->instructorchoicesendemailaddr == LTI_SETTING_ALWAYS)
    ) {
        $requestparams['lis_person_contact_email_primary'] = $USER->email;
    }

    return $requestparams;
}

/**
 * This function builds the request that must be sent to an LTI 2 tool provider
 *
 * @param object    $tool           Basic LTI tool object
 * @param array     $params         Custom launch parameters
 *
 * @return array                    Request details
 */
function lti_build_request_lti2($tool, $params) {

    $requestparams = array();

    $capabilities = lti_get_capabilities();
    $enabledcapabilities = explode("\n", $tool->enabledcapability);
    foreach ($enabledcapabilities as $capability) {
        if (array_key_exists($capability, $capabilities)) {
            $val = $capabilities[$capability];
            if ($val && (substr($val, 0, 1) != '$')) {
                if (isset($params[$val])) {
                    $requestparams[$capabilities[$capability]] = $params[$capabilities[$capability]];
                }
            }
        }
    }

    return $requestparams;

}

/**
 * This function builds the standard parameters for an LTI 1 or 2 request that must be sent to the tool producer
 *
 * @param stdClass  $instance       Basic LTI instance object
 * @param string    $orgid          Organisation ID
 * @param boolean   $islti2         True if an LTI 2 tool is being launched
 * @param string    $messagetype    The request message type. Defaults to basic-lti-launch-request if empty.
 *
 * @return array                    Request details
 * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
 * @see lti_build_standard_message()
 */
function lti_build_standard_request($instance, $orgid, $islti2, $messagetype = 'basic-lti-launch-request') {
    if (!$islti2) {
        $ltiversion = LTI_VERSION_1;
    } else {
        $ltiversion = LTI_VERSION_2;
    }
    return lti_build_standard_message($instance, $orgid, $ltiversion, $messagetype);
}

/**
 * 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 string    $orgid          Organisation ID
 * @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($instance, $orgid, $ltiversion, $messagetype = 'basic-lti-launch-request') {
    global $CFG;

    $requestparams = array();

    if ($instance) {
        $requestparams['resource_link_id'] = $instance->id;
        if (property_exists($instance, 'resource_link_id') and !empty($instance->resource_link_id)) {
            $requestparams['resource_link_id'] = $instance->resource_link_id;
        }
    }

    $requestparams['launch_presentation_locale'] = current_language();

    // Make sure we let the tool know what LMS they are being called from.
    $requestparams['ext_lms'] = 'moodle-2';
    $requestparams['tool_consumer_info_product_family_code'] = 'moodle';
    $requestparams['tool_consumer_info_version'] = strval($CFG->version);

    // 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;

    if ($orgid) {
        $requestparams["tool_consumer_instance_guid"] = $orgid;
    }
    if (!empty($CFG->mod_lti_institution_name)) {
        $requestparams['tool_consumer_instance_name'] = trim(html_to_text($CFG->mod_lti_institution_name, 0));
    } else {
        $requestparams['tool_consumer_instance_name'] = get_site()->shortname;
    }
    $requestparams['tool_consumer_instance_description'] = trim(html_to_text(get_site()->fullname, 0));

    return $requestparams;
}

/**
 * This function builds the custom parameters
 *
 * @param object    $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, $islti2) {

    // 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, $islti2);
    }
    if ($instructorcustomstr) {
        $custom = array_merge(lti_split_custom_parameters($toolproxy, $tool, $params,
            $instructorcustomstr, $islti2), $custom);
    }
    if ($islti2) {
        $custom = array_merge(lti_split_custom_parameters($toolproxy, $tool, $params,
            $tool->parameter, true), $custom);
        $settings = lti_get_tool_settings($tool->toolproxyid);
        $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
        if (!empty($instance->course)) {
            $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course);
            $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
            if (!empty($instance->id)) {
                $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course, $instance->id);
                $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
            }
        }
    }

    return $custom;
}

/**
 * Builds a standard LTI Content-Item selection request.
 *
 * @param int $id The tool type ID.
 * @param stdClass $course The course object.
 * @param moodle_url $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 moodle_exception When the LTI tool type does not exist.`
 * @throws coding_exception For invalid media type and presentation target parameters.
 */
function lti_build_content_item_selection_request($id, $course, moodle_url $returnurl, $title = '', $text = '', $mediatypes = [],
                                                  $presentationtargets = [], $autocreate = false, $multiple = true,
                                                  $unsigned = false, $canconfirm = false, $copyadvice = false, $nonce = '') {
    global $USER;

    $tool = lti_get_type($id);
    // Validate parameters.
    if (!$tool) {
        throw new moodle_exception('errortooltypenotfound', 'mod_lti');
    }
    if (!is_array($mediatypes)) {
        throw new coding_exception('The list of accepted media types should be in an array');
    }
    if (!is_array($presentationtargets)) {
        throw new coding_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 = $tool->name;
    }

    $typeconfig = lti_get_type_config($id);
    $key = '';
    $secret = '';
    $islti2 = false;
    $islti13 = false;
    if (isset($tool->toolproxyid)) {
        $islti2 = true;
        $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
        $key = $toolproxy->guid;
        $secret = $toolproxy->secret;
    } else {
        $islti13 = $tool->ltiversion === LTI_VERSION_1P3;
        $toolproxy = null;
        if ($islti13 && !empty($tool->clientid)) {
            $key = $tool->clientid;
        } else if (!$islti13 && !empty($typeconfig['resourcekey'])) {
            $key = $typeconfig['resourcekey'];
        }
        if (!empty($typeconfig['password'])) {
            $secret = $typeconfig['password'];
        }
    }
    $tool->enabledcapability = '';
    if (!empty($typeconfig['enabledcapability_ContentItemSelectionRequest'])) {
        $tool->enabledcapability = $typeconfig['enabledcapability_ContentItemSelectionRequest'];
    }

    $tool->parameter = '';
    if (!empty($typeconfig['parameter_ContentItemSelectionRequest'])) {
        $tool->parameter = $typeconfig['parameter_ContentItemSelectionRequest'];
    }

    // Set the tool URL.
    if (!empty($typeconfig['toolurl_ContentItemSelectionRequest'])) {
        $toolurl = new moodle_url($typeconfig['toolurl_ContentItemSelectionRequest']);
    } else {
        $toolurl = new moodle_url($typeconfig['toolurl']);
    }

    // Check if SSL is forced.
    if (!empty($typeconfig['forcessl'])) {
        // Make sure the tool URL is set to https.
        if (strtolower($toolurl->get_scheme()) === 'http') {
            $toolurl->set_scheme('https');
        }
        // Make sure the return URL is set to https.
        if (strtolower($returnurl->get_scheme()) === 'http') {
            $returnurl->set_scheme('https');
        }
    }
    $toolurlout = $toolurl->out(false);

    // Get base request parameters.
    $instance = new stdClass();
    $instance->course = $course->id;
    $requestparams = lti_build_request($instance, $typeconfig, $course, $id, $islti2);

    // Get LTI2-specific request parameters and merge to the request parameters if applicable.
    if ($islti2) {
        $lti2params = lti_build_request_lti2($tool, $requestparams);
        $requestparams = array_merge($requestparams, $lti2params);
    }

    // Get standard request parameters and merge to the request parameters.
    $orgid = lti_get_organizationid($typeconfig);
    $standardparams = lti_build_standard_message(null, $orgid, $tool->ltiversion, 'ContentItemSelectionRequest');
    $requestparams = array_merge($requestparams, $standardparams);

    // Get custom request parameters and merge to the request parameters.
    $customstr = '';
    if (!empty($typeconfig['customparameters'])) {
        $customstr = $typeconfig['customparameters'];
    }
    $customparams = lti_build_custom_parameters($toolproxy, $tool, $instance, $requestparams, $customstr, '', $islti2);
    $requestparams = array_merge($requestparams, $customparams);

    // Add the parameters configured by the LTI services.
    if ($id && !$islti2) {
        $services = lti_get_services();
        foreach ($services as $service) {
            $serviceparameters = $service->get_launch_parameters('ContentItemSelectionRequest',
                $course->id, $USER->id , $id);
            foreach ($serviceparameters as $paramkey => $paramvalue) {
                $requestparams['custom_' . $paramkey] = lti_parse_custom_parameter($toolproxy, $tool, $requestparams, $paramvalue,
                    $islti2);
            }
        }
    }

    // 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', [$instance, $toolurlout, $requestparams], []);

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

    if (!$islti13) {
        // Media types. Set to ltilink by default if empty.
        if (empty($mediatypes)) {
            $mediatypes = [
                'application/vnd.ims.lti.v1.ltilink',
            ];
        }
        $requestparams['accept_media_types'] = implode(',', $mediatypes);
    } else {
        // Only LTI links are currently supported.
        $requestparams['accept_types'] = 'ltiResourceLink';
    }

    // Presentation targets. Supports frame, iframe, window by default if empty.
    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->out(false);
    $requestparams['title'] = $title;
    $requestparams['text'] = $text;
    if (!$islti13) {
        $signedparams = lti_sign_parameters($requestparams, $toolurlout, 'POST', $key, $secret);
    } else {
        $signedparams = lti_sign_jwt($requestparams, $toolurlout, $key, $id, $nonce);
    }
    $toolurlparams = $toolurl->params();

    // Strip querystring params in endpoint url from $signedparams to avoid duplication.
    if (!empty($toolurlparams) && !empty($signedparams)) {
        foreach (array_keys($toolurlparams) as $paramname) {
            if (isset($signedparams[$paramname])) {
                unset($signedparams[$paramname]);
            }
        }
    }

    // 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 = $toolurlout;

    return $result;
}

/**
 * Verifies the OAuth signature of an incoming message.
 *
 * @param int $typeid The tool type ID.
 * @param string $consumerkey The consumer key.
 * @return stdClass Tool type
 * @throws moodle_exception
 * @throws lti\OAuthException
 */
function lti_verify_oauth_signature($typeid, $consumerkey) {
    $tool = lti_get_type($typeid);
    // Validate parameters.
    if (!$tool) {
        throw new moodle_exception('errortooltypenotfound', 'mod_lti');
    }
    $typeconfig = lti_get_type_config($typeid);

    if (isset($tool->toolproxyid)) {
        $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
        $key = $toolproxy->guid;
        $secret = $toolproxy->secret;
    } else {
        $toolproxy = null;
        if (!empty($typeconfig['resourcekey'])) {
            $key = $typeconfig['resourcekey'];
        } else {
            $key = '';
        }
        if (!empty($typeconfig['password'])) {
            $secret = $typeconfig['password'];
        } else {
            $secret = '';
        }
    }

    if ($consumerkey !== $key) {
        throw new moodle_exception('errorincorrectconsumerkey', 'mod_lti');
    }

    $store = new lti\TrivialOAuthDataStore();
    $store->add_consumer($key, $secret);
    $server = new lti\OAuthServer($store);
    $method = new lti\OAuthSignatureMethod_HMAC_SHA1();
    $server->add_signature_method($method);
    $request = lti\OAuthRequest::from_request();
    try {
        $server->verify_request($request);
    } catch (lti\OAuthException $e) {
        throw new lti\OAuthException("OAuth signature failed: " . $e->getMessage());
    }

    return $tool;
}

/**
 * Verifies the JWT signature using a JWK keyset.
 *
 * @param string $jwtparam JWT parameter value.
 * @param string $keyseturl The tool keyseturl.
 * @param string $clientid The tool client id.
 *
 * @return object The JWT's payload as a PHP object
 * @throws moodle_exception
 * @throws UnexpectedValueException     Provided JWT was invalid
 * @throws SignatureInvalidException    Provided JWT was invalid because the signature verification failed
 * @throws BeforeValidException         Provided JWT is trying to be used before it's eligible as defined by 'nbf'
 * @throws BeforeValidException         Provided JWT is trying to be used before it's been created as defined by 'iat'
 * @throws ExpiredException             Provided JWT has since expired, as defined by the 'exp' claim
 */
function lti_verify_with_keyset($jwtparam, $keyseturl, $clientid) {
    // Attempts to retrieve cached keyset.
    $cache = cache::make('mod_lti', 'keyset');
    $keyset = $cache->get($clientid);

    try {
        if (empty($keyset)) {
            throw new moodle_exception('errornocachedkeysetfound', 'mod_lti');
        }
        $keysetarr = json_decode($keyset, true);
        $keys = JWK::parseKeySet($keysetarr);
        $jwt = JWT::decode($jwtparam, $keys);
    } catch (Exception $e) {
        // Something went wrong, so attempt to update cached keyset and then try again.
        $keyset = download_file_content($keyseturl);
        $keysetarr = json_decode($keyset, true);

        // Fix for firebase/php-jwt's dependency on the optional 'alg' property in the JWK.
        // The fix_jwks_alg() call only fixes a single, matched key and will leave others present (which may be missing alg too),
        // Remaining keys missing alg are excluded since they cannot be used for decoding anyway (no match to JWT kid).
        $keysetarr = jwks_helper::fix_jwks_alg($keysetarr, $jwtparam);
        $keysetarr['keys'] = array_filter($keysetarr['keys'], fn($key) => isset($key['alg']));

        // JWK::parseKeySet uses RS256 algorithm by default.
        $keys = JWK::parseKeySet($keysetarr);
        $jwt = JWT::decode($jwtparam, $keys);
        // If sucessful, updates the cached keyset.
        $cache->set($clientid, $keyset);
    }
    return $jwt;
}

/**
 * Verifies the JWT signature of an incoming message.
 *
 * @param int $typeid The tool type ID.
 * @param string $consumerkey The consumer key.
 * @param string $jwtparam JWT parameter value
 *
 * @return stdClass Tool type
 * @throws moodle_exception
 * @throws UnexpectedValueException     Provided JWT was invalid
 * @throws SignatureInvalidException    Provided JWT was invalid because the signature verification failed
 * @throws BeforeValidException         Provided JWT is trying to be used before it's eligible as defined by 'nbf'
 * @throws BeforeValidException         Provided JWT is trying to be used before it's been created as defined by 'iat'
 * @throws ExpiredException             Provided JWT has since expired, as defined by the 'exp' claim
 */
function lti_verify_jwt_signature($typeid, $consumerkey, $jwtparam) {
    $tool = lti_get_type($typeid);

    // Validate parameters.
    if (!$tool) {
        throw new moodle_exception('errortooltypenotfound', 'mod_lti');
    }
    if (isset($tool->toolproxyid)) {
        throw new moodle_exception('JWT security not supported with LTI 2');
    }

    $typeconfig = lti_get_type_config($typeid);

    $key = $tool->clientid ?? '';

    if ($consumerkey !== $key) {
        throw new moodle_exception('errorincorrectconsumerkey', 'mod_lti');
    }

    if (empty($typeconfig['keytype']) || $typeconfig['keytype'] === LTI_RSA_KEY) {
        $publickey = $typeconfig['publickey'] ?? '';
        if (empty($publickey)) {
            throw new moodle_exception('No public key configured');
        }
        // Attemps to verify jwt with RSA key.
        JWT::decode($jwtparam, new Key($publickey, 'RS256'));
    } else if ($typeconfig['keytype'] === LTI_JWK_KEYSET) {
        $keyseturl = $typeconfig['publickeyset'] ?? '';
        if (empty($keyseturl)) {
            throw new moodle_exception('No public keyset configured');
        }
        // Attempts to verify jwt with jwk keyset.
        lti_verify_with_keyset($jwtparam, $keyseturl, $tool->clientid);
    } else {
        throw new moodle_exception('Invalid public key type');
    }

    return $tool;
}

/**
 * Converts an array of custom parameters to a new line separated string.
 *
 * @param object $params list of params to concatenate
 *
 * @return string
 */
function params_to_string(object $params) {
    $customparameters = [];
    foreach ($params as $key => $value) {
        $customparameters[] = "{$key}={$value}";
    }
    return implode("\n", $customparameters);
}

/**
 * Converts LTI 1.1 Content Item for LTI Link to Form data.
 *
 * @param object $tool Tool for which the item is created for.
 * @param object $typeconfig The tool configuration.
 * @param object $item Item populated from JSON to be converted to Form form
 *
 * @return stdClass Form config for the item
 */
function content_item_to_form(object $tool, object $typeconfig, object $item): stdClass {
    global $OUTPUT;

    $config = new stdClass();
    $config->name = '';
    if (isset($item->title)) {
        $config->name = $item->title;
    }
    if (empty($config->name)) {
        $config->name = $tool->name;
    }
    if (isset($item->text)) {
        $config->introeditor = [
            'text' => $item->text,
            'format' => FORMAT_PLAIN
        ];
    } else {
        $config->introeditor = [
            'text' => '',
            'format' => FORMAT_PLAIN
        ];
    }
    if (isset($item->icon->{'@id'})) {
        $iconurl = new moodle_url($item->icon->{'@id'});
        // Assign item's icon URL to secureicon or icon depending on its scheme.
        if (strtolower($iconurl->get_scheme()) === 'https') {
            $config->secureicon = $iconurl->out(false);
        } else {
            $config->icon = $iconurl->out(false);
        }
    }
    if (isset($item->url)) {
        $url = new moodle_url($item->url);
        $config->toolurl = $url->out(false);
        $config->typeid = 0;
    } else {
        $config->typeid = $tool->id;
    }
    $config->instructorchoiceacceptgrades = LTI_SETTING_NEVER;
    $islti2 = $tool->ltiversion === LTI_VERSION_2;
    if (!$islti2 && isset($typeconfig->lti_acceptgrades)) {
        $acceptgrades = $typeconfig->lti_acceptgrades;
        if ($acceptgrades == LTI_SETTING_ALWAYS) {
            // We create a line item regardless if the definition contains one or not.
            $config->instructorchoiceacceptgrades = LTI_SETTING_ALWAYS;
            $config->grade_modgrade_point = 100;
        }
        if ($acceptgrades == LTI_SETTING_DELEGATE || $acceptgrades == LTI_SETTING_ALWAYS) {
            if (isset($item->lineItem)) {
                $lineitem = $item->lineItem;
                $config->instructorchoiceacceptgrades = LTI_SETTING_ALWAYS;
                $maxscore = 100;
                if (isset($lineitem->scoreConstraints)) {
                    $sc = $lineitem->scoreConstraints;
                    if (isset($sc->totalMaximum)) {
                        $maxscore = $sc->totalMaximum;
                    } else if (isset($sc->normalMaximum)) {
                        $maxscore = $sc->normalMaximum;
                    }
                }
                $config->grade_modgrade_point = $maxscore;
                $config->lineitemresourceid = '';
                $config->lineitemtag = '';
                $config->lineitemsubreviewurl = '';
                $config->lineitemsubreviewparams = '';
                if (isset($lineitem->assignedActivity) && isset($lineitem->assignedActivity->activityId)) {
                    $config->lineitemresourceid = $lineitem->assignedActivity->activityId?:'';
                }
                if (isset($lineitem->tag)) {
                    $config->lineitemtag = $lineitem->tag?:'';
                }
                if (isset($lineitem->submissionReview)) {
                    $subreview = $lineitem->submissionReview;
                    $config->lineitemsubreviewurl = 'DEFAULT';
                    if (!empty($subreview->url)) {
                        $config->lineitemsubreviewurl = $subreview->url;
                    }
                    if (isset($subreview->custom)) {
                        $config->lineitemsubreviewparams = params_to_string($subreview->custom);
                    }
                }
            }
        }
    }
    $config->instructorchoicesendname = LTI_SETTING_NEVER;
    $config->instructorchoicesendemailaddr = LTI_SETTING_NEVER;

    // Since 4.3, the launch container is dictated by the value set in tool configuration and isn't controllable by content items.
    $config->launchcontainer = LTI_LAUNCH_CONTAINER_DEFAULT;

    if (isset($item->custom)) {
        $config->instructorcustomparameters = params_to_string($item->custom);
    }

    // Pass an indicator to the relevant form field.
    $config->selectcontentindicator = $OUTPUT->pix_icon('i/valid', get_string('yes')) . get_string('contentselected', 'mod_lti');

    return $config;
}

/**
 * Processes the tool provider's response to the ContentItemSelectionRequest and builds the configuration data from the
 * selected content item. This configuration data can be then used when adding a tool into the course.
 *
 * @param int $typeid The tool type ID.
 * @param string $messagetype The value for the lti_message_type parameter.
 * @param string $ltiversion The value for the lti_version parameter.
 * @param string $consumerkey The consumer key.
 * @param string $contentitemsjson The JSON string for the content_items parameter.
 * @return stdClass The array of module information objects.
 * @throws moodle_exception
 * @throws lti\OAuthException
 */
function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiversion, $consumerkey, $contentitemsjson) {
    $tool = lti_get_type($typeid);
    // Validate parameters.
    if (!$tool) {
        throw new moodle_exception('errortooltypenotfound', 'mod_lti');
    }
    // Check lti_message_type. Show debugging if it's not set to ContentItemSelection.
    // No need to throw exceptions for now since lti_message_type does not seem to be used in this processing at the moment.
    if ($messagetype !== 'ContentItemSelection') {
        debugging("lti_message_type is invalid: {$messagetype}. It should be set to 'ContentItemSelection'.",
            DEBUG_DEVELOPER);
    }

    // Check LTI versions from our side and the response's side. Show debugging if they don't match.
    // No need to throw exceptions for now since LTI version does not seem to be used in this processing at the moment.
    $expectedversion = $tool->ltiversion;
    $islti2 = ($expectedversion === LTI_VERSION_2);
    if ($ltiversion !== $expectedversion) {
        debugging("lti_version from response does not match the tool's configuration. Tool: {$expectedversion}," .
            " Response: {$ltiversion}", DEBUG_DEVELOPER);
    }

    $items = json_decode($contentitemsjson);
    if (empty($items)) {
        throw new moodle_exception('errorinvaliddata', 'mod_lti', '', $contentitemsjson);
    }
    if (!isset($items->{'@graph'}) || !is_array($items->{'@graph'})) {
        throw new moodle_exception('errorinvalidresponseformat', 'mod_lti');
    }

    $config = null;
    $items = $items->{'@graph'};
    if (!empty($items)) {
        $typeconfig = lti_get_type_type_config($tool->id);
        if (count($items) == 1) {
            $config = content_item_to_form($tool, $typeconfig, $items[0]);
        } else {
            $multiple = [];
            foreach ($items as $item) {
                $multiple[] = content_item_to_form($tool, $typeconfig, $item);
            }
            $config = new stdClass();
            $config->multiple = $multiple;
        }
    }
    return $config;
}

/**
 * Converts the new Deep-Linking format for Content-Items to the old format.
 *
 * @param string $param JSON string representing new Deep-Linking format
 * @return string  JSON representation of content-items
 */
function lti_convert_content_items($param) {
    $items = array();
    $json = json_decode($param);
    if (!empty($json) && is_array($json)) {
        foreach ($json as $item) {
            if (isset($item->type)) {
                $newitem = clone $item;
                switch ($item->type) {
                    case 'ltiResourceLink':
                        $newitem->{'@type'} = 'LtiLinkItem';
                        $newitem->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
                        break;
                    case 'link':
                    case 'rich':
                        $newitem->{'@type'} = 'ContentItem';
                        $newitem->mediaType = 'text/html';
                        break;
                    case 'file':
                        $newitem->{'@type'} = 'FileItem';
                        break;
                }
                unset($newitem->type);
                if (isset($item->html)) {
                    $newitem->text = $item->html;
                    unset($newitem->html);
                }
                if (isset($item->iframe)) {
                    // DeepLinking allows multiple options to be declared as supported.
                    // We favor iframe over new window if both are specified.
                    $newitem->placementAdvice = new stdClass();
                    $newitem->placementAdvice->presentationDocumentTarget = 'iframe';
                    if (isset($item->iframe->width)) {
                        $newitem->placementAdvice->displayWidth = $item->iframe->width;
                    }
                    if (isset($item->iframe->height)) {
                        $newitem->placementAdvice->displayHeight = $item->iframe->height;
                    }
                    unset($newitem->iframe);
                    unset($newitem->window);
                } else if (isset($item->window)) {
                    $newitem->placementAdvice = new stdClass();
                    $newitem->placementAdvice->presentationDocumentTarget = 'window';
                    if (isset($item->window->targetName)) {
                        $newitem->placementAdvice->windowTarget = $item->window->targetName;
                    }
                    if (isset($item->window->width)) {
                        $newitem->placementAdvice->displayWidth = $item->window->width;
                    }
                    if (isset($item->window->height)) {
                        $newitem->placementAdvice->displayHeight = $item->window->height;
                    }
                    unset($newitem->window);
                } else if (isset($item->presentation)) {
                    // This may have been part of an early draft but is not in the final spec
                    // so keeping it around for now in case it's actually been used.
                    $newitem->placementAdvice = new stdClass();
                    if (isset($item->presentation->documentTarget)) {
                        $newitem->placementAdvice->presentationDocumentTarget = $item->presentation->documentTarget;
                    }
                    if (isset($item->presentation->windowTarget)) {
                        $newitem->placementAdvice->windowTarget = $item->presentation->windowTarget;
                    }
                    if (isset($item->presentation->width)) {
                        $newitem->placementAdvice->dislayWidth = $item->presentation->width;
                    }
                    if (isset($item->presentation->height)) {
                        $newitem->placementAdvice->dislayHeight = $item->presentation->height;
                    }
                    unset($newitem->presentation);
                }
                if (isset($item->icon) && isset($item->icon->url)) {
                    $newitem->icon->{'@id'} = $item->icon->url;
                    unset($newitem->icon->url);
                }
                if (isset($item->thumbnail) && isset($item->thumbnail->url)) {
                    $newitem->thumbnail->{'@id'} = $item->thumbnail->url;
                    unset($newitem->thumbnail->url);
                }
                if (isset($item->lineItem)) {
                    unset($newitem->lineItem);
                    $newitem->lineItem = new stdClass();
                    $newitem->lineItem->{'@type'} = 'LineItem';
                    $newitem->lineItem->reportingMethod = 'http://purl.imsglobal.org/ctx/lis/v2p1/Result#totalScore';
                    if (isset($item->lineItem->label)) {
                        $newitem->lineItem->label = $item->lineItem->label;
                    }
                    if (isset($item->lineItem->resourceId)) {
                        $newitem->lineItem->assignedActivity = new stdClass();
                        $newitem->lineItem->assignedActivity->activityId = $item->lineItem->resourceId;
                    }
                    if (isset($item->lineItem->tag)) {
                        $newitem->lineItem->tag = $item->lineItem->tag;
                    }
                    if (isset($item->lineItem->scoreMaximum)) {
                        $newitem->lineItem->scoreConstraints = new stdClass();
                        $newitem->lineItem->scoreConstraints->{'@type'} = 'NumericLimits';
                        $newitem->lineItem->scoreConstraints->totalMaximum = $item->lineItem->scoreMaximum;
                    }
                    if (isset($item->lineItem->submissionReview)) {
                        $newitem->lineItem->submissionReview = $item->lineItem->submissionReview;
                    }
                }
                $items[] = $newitem;
            }
        }
    }

    $newitems = new stdClass();
    $newitems->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem';
    $newitems->{'@graph'} = $items;

    return json_encode($newitems);
}

function lti_get_tool_table($tools, $id) {
    global $OUTPUT;
    $html = '';

    $typename = get_string('typename', 'lti');
    $baseurl = get_string('baseurl', 'lti');
    $action = get_string('action', 'lti');
    $createdon = get_string('createdon', 'lti');

    if (!empty($tools)) {
        $html .= "
        <div id=\"{$id}_tools_container\" style=\"margin-top:.5em;margin-bottom:.5em\">
            <table id=\"{$id}_tools\">
                <thead>
                    <tr>
                        <th>$typename</th>
                        <th>$baseurl</th>
                        <th>$createdon</th>
                        <th>$action</th>
                    </tr>
                </thead>
        ";

        foreach ($tools as $type) {
            $date = userdate($type->timecreated, get_string('strftimedatefullshort', 'core_langconfig'));
            $accept = get_string('accept', 'lti');
            $update = get_string('update', 'lti');
            $delete = get_string('delete', 'lti');

            if (empty($type->toolproxyid)) {
                $baseurl = new \moodle_url('/mod/lti/typessettings.php', array(
                        'action' => 'accept',
                        'id' => $type->id,
                        'sesskey' => sesskey(),
                        'tab' => $id
                    ));
                $ref = $type->baseurl;
            } else {
                $baseurl = new \moodle_url('/mod/lti/toolssettings.php', array(
                        'action' => 'accept',
                        'id' => $type->id,
                        'sesskey' => sesskey(),
                        'tab' => $id
                    ));
                $ref = $type->tpname;
            }

            $accepthtml = $OUTPUT->action_icon($baseurl,
                    new \pix_icon('t/check', $accept, '', array('class' => 'iconsmall')), null,
                    array('title' => $accept, 'class' => 'editing_accept'));

            $deleteaction = 'delete';

            if ($type->state == LTI_TOOL_STATE_CONFIGURED) {
                $accepthtml = '';
            }

            if ($type->state != LTI_TOOL_STATE_REJECTED) {
                $deleteaction = 'reject';
                $delete = get_string('reject', 'lti');
            }

            $updateurl = clone($baseurl);
            $updateurl->param('action', 'update');
            $updatehtml = $OUTPUT->action_icon($updateurl,
                    new \pix_icon('t/edit', $update, '', array('class' => 'iconsmall')), null,
                    array('title' => $update, 'class' => 'editing_update'));

            if (($type->state != LTI_TOOL_STATE_REJECTED) || empty($type->toolproxyid)) {
                $deleteurl = clone($baseurl);
                $deleteurl->param('action', $deleteaction);
                $deletehtml = $OUTPUT->action_icon($deleteurl,
                        new \pix_icon('t/delete', $delete, '', array('class' => 'iconsmall')), null,
                        array('title' => $delete, 'class' => 'editing_delete'));
            } else {
                $deletehtml = '';
            }
            $html .= "
            <tr>
                <td>
                    {$type->name}
                </td>
                <td>
                    {$ref}
                </td>
                <td>
                    {$date}
                </td>
                <td align=\"center\">
                    {$accepthtml}{$updatehtml}{$deletehtml}
                </td>
            </tr>
            ";
        }
        $html .= '</table></div>';
    } else {
        $html .= get_string('no_' . $id, 'lti');
    }

    return $html;
}

/**
 * This function builds the tab for a category of tool proxies
 *
 * @param object    $toolproxies    Tool proxy instance objects
 * @param string    $id             Category ID
 *
 * @return string                   HTML for tab
 */
function lti_get_tool_proxy_table($toolproxies, $id) {
    global $OUTPUT;

    if (!empty($toolproxies)) {
        $typename = get_string('typename', 'lti');
        $url = get_string('registrationurl', 'lti');
        $action = get_string('action', 'lti');
        $createdon = get_string('createdon', 'lti');

        $html = <<< EOD
        <div id="{$id}_tool_proxies_container" style="margin-top: 0.5em; margin-bottom: 0.5em">
            <table id="{$id}_tool_proxies">
                <thead>
                    <tr>
                        <th>{$typename}</th>
                        <th>{$url}</th>
                        <th>{$createdon}</th>
                        <th>{$action}</th>
                    </tr>
                </thead>
EOD;
        foreach ($toolproxies as $toolproxy) {
            $date = userdate($toolproxy->timecreated, get_string('strftimedatefullshort', 'core_langconfig'));
            $accept = get_string('register', 'lti');
            $update = get_string('update', 'lti');
            $delete = get_string('delete', 'lti');

            $baseurl = new \moodle_url('/mod/lti/registersettings.php', array(
                    'action' => 'accept',
                    'id' => $toolproxy->id,
                    'sesskey' => sesskey(),
                    'tab' => $id
                ));

            $registerurl = new \moodle_url('/mod/lti/register.php', array(
                    'id' => $toolproxy->id,
                    'sesskey' => sesskey(),
                    'tab' => 'tool_proxy'
                ));

            $accepthtml = $OUTPUT->action_icon($registerurl,
                    new \pix_icon('t/check', $accept, '', array('class' => 'iconsmall')), null,
                    array('title' => $accept, 'class' => 'editing_accept'));

            $deleteaction = 'delete';

            if ($toolproxy->state != LTI_TOOL_PROXY_STATE_CONFIGURED) {
                $accepthtml = '';
            }

            if (($toolproxy->state == LTI_TOOL_PROXY_STATE_CONFIGURED) || ($toolproxy->state == LTI_TOOL_PROXY_STATE_PENDING)) {
                $delete = get_string('cancel', 'lti');
            }

            $updateurl = clone($baseurl);
            $updateurl->param('action', 'update');
            $updatehtml = $OUTPUT->action_icon($updateurl,
                    new \pix_icon('t/edit', $update, '', array('class' => 'iconsmall')), null,
                    array('title' => $update, 'class' => 'editing_update'));

            $deleteurl = clone($baseurl);
            $deleteurl->param('action', $deleteaction);
            $deletehtml = $OUTPUT->action_icon($deleteurl,
                    new \pix_icon('t/delete', $delete, '', array('class' => 'iconsmall')), null,
                    array('title' => $delete, 'class' => 'editing_delete'));
            $html .= <<< EOD
            <tr>
                <td>
                    {$toolproxy->name}
                </td>
                <td>
                    {$toolproxy->regurl}
                </td>
                <td>
                    {$date}
                </td>
                <td align="center">
                    {$accepthtml}{$updatehtml}{$deletehtml}
                </td>
            </tr>
EOD;
        }
        $html .= '</table></div>';
    } else {
        $html = get_string('no_' . $id, 'lti');
    }

    return $html;
}

/**
 * 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(core_text::substr($param, $pos + 1, strlen($param)));
            if (substr($value, 0, 1) == '$') {
                $value = substr($value, 1);
                if (!in_array($value, $enabledcapabilities)) {
                    $enabledcapabilities[] = $value;
                }
            }
        }
    }
    return $enabledcapabilities;
}

/**
 * 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(core_text::substr($line, 0, $pos));
        $val = trim(core_text::substr($line, $pos + 1, strlen($line)));
        $retval[$key] = $val;
    }
    return $retval;
}

/**
 * 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, $islti2 = false) {
    $splitted = lti_split_parameters($customstr);
    $retval = array();
    foreach ($splitted as $key => $val) {
        $val = lti_parse_custom_parameter($toolproxy, $tool, $params, $val, $islti2);
        $key2 = lti_map_keyname($key);
        $retval['custom_'.$key2] = $val;
        if (($islti2 || ($tool->ltiversion === LTI_VERSION_1P3)) && ($key != $key2)) {
            $retval['custom_'.$key] = $val;
        }
    }
    return $retval;
}

/**
 * Adds the custom parameters to an array
 *
 * @param object    $toolproxy      Tool proxy instance object
 * @param object    $tool           Tool instance object
 * @param array     $params         LTI launch parameters
 * @param array     $parameters     Array containing the parameters
 *
 * @return array    Array of custom parameters
 */
function lti_get_custom_parameters($toolproxy, $tool, $params, $parameters) {
    $retval = array();
    foreach ($parameters as $key => $val) {
        $key2 = lti_map_keyname($key);
        $val = lti_parse_custom_parameter($toolproxy, $tool, $params, $val, true);
        $retval['custom_'.$key2] = $val;
        if ($key != $key2) {
            $retval['custom_'.$key] = $val;
        }
    }
    return $retval;
}

/**
 * Parse a custom parameter to replace any substitution variables
 *
 * @param object    $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, $params, $value, $islti2) {
    // This is required as {${$valarr[0]}->{$valarr[1]}}" may be using the USER or COURSE var.
    global $USER, $COURSE;

    if ($value) {
        if (substr($value, 0, 1) == '\\') {
            $value = substr($value, 1);
        } else if (substr($value, 0, 1) == '$') {
            $value1 = substr($value, 1);
            $enabledcapabilities = lti_get_enabled_capabilities($tool);
            if (!$islti2 || in_array($value1, $enabledcapabilities)) {
                $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);
                            $value = format_string($value);
                        }
                    } else {
                        $value = lti_calculate_custom_parameter($value1);
                    }
                } else {
                    $val = $value;
                    $services = lti_get_services();
                    foreach ($services as $service) {
                        $service->set_tool_proxy($toolproxy);
                        $service->set_type($tool);
                        $value = $service->parse_value($val);
                        if ($val != $value) {
                            break;
                        }
                    }
                }
            }
        }
    }
    return $value;
}

/**
 * Calculates the value of a custom parameter that has not been specified earlier
 *
 * @param string    $value          Custom parameter value
 *
 * @return string Calculated value of custom parameter
 */
function lti_calculate_custom_parameter($value) {
    global $USER, $COURSE;

    switch ($value) {
        case 'Moodle.Person.userGroupIds':
            return implode(",", groups_get_user_groups($COURSE->id, $USER->id)[0]);
        case 'Context.id.history':
            return implode(",", get_course_history($COURSE));
        case 'CourseSection.timeFrame.begin':
            if (empty($COURSE->startdate)) {
                return "";
            }
            $dt = new DateTime("@$COURSE->startdate", new DateTimeZone('UTC'));
            return $dt->format(DateTime::ATOM);
        case 'CourseSection.timeFrame.end':
            if (empty($COURSE->enddate)) {
                return "";
            }
            $dt = new DateTime("@$COURSE->enddate", new DateTimeZone('UTC'));
            return $dt->format(DateTime::ATOM);
    }
    return null;
}

/**
 * Build the history chain for this course using the course originalcourseid.
 *
 * @param object $course course for which the history is returned.
 *
 * @return array ids of the source course in ancestry order, immediate parent 1st.
 */
function get_course_history($course) {
    global $DB;
    $history = [];
    $parentid = $course->originalcourseid;
    while (!empty($parentid) && !in_array($parentid, $history)) {
        $history[] = $parentid;
        $parentid = $DB->get_field('course', 'originalcourseid', array('id' => $parentid));
    }
    return $history;
}

/**
 * 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 = core_text::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;
}

/**
 * Gets the IMS role string for the specified user and LTI course module.
 *
 * @param mixed    $user      User object or user id
 * @param int      $cmid      The course module id of the LTI activity
 * @param int      $courseid  The course id of the LTI activity
 * @param boolean  $islti2    True if an LTI 2 tool is being launched
 *
 * @return string A role string suitable for passing with an LTI launch
 */
function lti_get_ims_role($user, $cmid, $courseid, $islti2) {
    $roles = array();

    if (empty($cmid)) {
        // If no cmid is passed, check if the user is a teacher in the course
        // This allows other modules to programmatically "fake" a launch without
        // a real LTI instance.
        $context = context_course::instance($courseid);

        if (has_capability('moodle/course:manageactivities', $context, $user)) {
            array_push($roles, 'Instructor');
        } else {
            array_push($roles, 'Learner');
        }
    } else {
        $context = context_module::instance($cmid);

        if (has_capability('mod/lti:manage', $context)) {
            array_push($roles, 'Instructor');
        } else {
            array_push($roles, 'Learner');
        }
    }

    if (!is_role_switched($courseid) && (is_siteadmin($user)) || has_capability('mod/lti:admin', $context)) {
        // Make sure admins do not have the Learner role, then set admin role.
        $roles = array_diff($roles, array('Learner'));
        if (!$islti2) {
            array_push($roles, 'urn:lti:sysrole:ims/lis/Administrator', 'urn:lti:instrole:ims/lis/Administrator');
        } else {
            array_push($roles, 'http://purl.imsglobal.org/vocab/lis/v2/person#Administrator');
        }
    }

    return join(',', $roles);
}

/**
 * Returns configuration details for the tool
 *
 * @param int $typeid   Basic LTI tool typeid
 *
 * @return array        Tool Configuration
 */
function lti_get_type_config($typeid) {
    global $DB;

    $query = "SELECT name, value
                FROM {lti_types_config}
               WHERE typeid = :typeid1
           UNION ALL
              SELECT 'toolurl' AS name, baseurl AS value
                FROM {lti_types}
               WHERE id = :typeid2
           UNION ALL
              SELECT 'icon' AS name, icon AS value
                FROM {lti_types}
               WHERE id = :typeid3
           UNION ALL
              SELECT 'secureicon' AS name, secureicon AS value
                FROM {lti_types}
               WHERE id = :typeid4";

    $typeconfig = array();
    $configs = $DB->get_records_sql($query,
        array('typeid1' => $typeid, 'typeid2' => $typeid, 'typeid3' => $typeid, 'typeid4' => $typeid));

    if (!empty($configs)) {
        foreach ($configs as $config) {
            $typeconfig[$config->name] = $config->value;
        }
    }

    return $typeconfig;
}

function lti_get_tools_by_url($url, $state, $courseid = null) {
    $domain = lti_get_domain_from_url($url);

    return lti_get_tools_by_domain($domain, $state, $courseid);
}

function lti_get_tools_by_domain($domain, $state = null, $courseid = null) {
    global $DB, $SITE;

    $statefilter = '';
    $coursefilter = '';

    if ($state) {
        $statefilter = 'AND t.state = :state';
    }

    if ($courseid && $courseid != $SITE->id) {
        $coursefilter = 'OR t.course = :courseid';
    }

    $coursecategory = $DB->get_field('course', 'category', ['id' => $courseid]);
    $query = "SELECT t.*
                FROM {lti_types} t
           LEFT JOIN {lti_types_categories} tc on t.id = tc.typeid
               WHERE t.tooldomain = :tooldomain
                 AND (t.course = :siteid $coursefilter)
                 $statefilter
                 AND (tc.id IS NULL OR tc.categoryid = :categoryid)";

    return $DB->get_records_sql($query, [
            'courseid' => $courseid,
            'siteid' => $SITE->id,
            'tooldomain' => $domain,
            'state' => $state,
            'categoryid' => $coursecategory
        ]);
}

/**
 * Returns all basicLTI tools configured by the administrator
 *
 * @param int $course
 *
 * @return array
 */
function lti_filter_get_types($course) {
    global $DB;

    if (!empty($course)) {
        $where = "WHERE t.course = :course";
        $params = array('course' => $course);
    } else {
        $where = '';
        $params = array();
    }
    $query = "SELECT t.id, t.name, t.baseurl, t.state, t.toolproxyid, t.timecreated, tp.name tpname
                FROM {lti_types} t LEFT OUTER JOIN {lti_tool_proxies} tp ON t.toolproxyid = tp.id
                {$where}";
    return $DB->get_records_sql($query, $params);
}

/**
 * Given an array of tools, filter them based on their state
 *
 * @param array $tools An array of lti_types records
 * @param int $state One of the LTI_TOOL_STATE_* constants
 * @return array
 */
function lti_filter_tool_types(array $tools, $state) {
    $return = array();
    foreach ($tools as $key => $tool) {
        if ($tool->state == $state) {
            $return[$key] = $tool;
        }
    }
    return $return;
}

/**
 * Returns all lti types visible in this course
 *
 * @deprecated since Moodle 4.3
 * @param int $courseid The id of the course to retieve types for
 * @param array $coursevisible options for 'coursevisible' field,
 *        default [LTI_COURSEVISIBLE_PRECONFIGURED, LTI_COURSEVISIBLE_ACTIVITYCHOOSER]
 * @return stdClass[] All the lti types visible in the given course
 */
function lti_get_lti_types_by_course($courseid, $coursevisible = null) {
    debugging(__FUNCTION__ . '() is deprecated. Please use \mod_lti\local\types_helper::get_lti_types_by_course() instead.',
        DEBUG_DEVELOPER);

    global $USER;
    return \mod_lti\local\types_helper::get_lti_types_by_course($courseid, $USER->id, $coursevisible ?? []);
}

/**
 * Returns tool types for lti add instance and edit page
 *
 * @return array Array of lti types
 */
function lti_get_types_for_add_instance() {
    global $COURSE, $USER;

    // Always return the 'manual' type option, despite manual config being deprecated, so that we have it for legacy instances.
    $types = [(object) ['name' => get_string('automatic', 'lti'), 'course' => 0, 'toolproxyid' => null]];

    $preconfiguredtypes = \mod_lti\local\types_helper::get_lti_types_by_course($COURSE->id, $USER->id);
    foreach ($preconfiguredtypes as $type) {
        $types[$type->id] = $type;
    }

    return $types;
}

/**
 * Returns a list of configured types in the given course
 *
 * @param int $courseid The id of the course to retieve types for
 * @param int $sectionreturn section to return to for forming the URLs
 * @return array Array of lti types. Each element is object with properties: name, title, icon, help, helplink, link
 */
function lti_get_configured_types($courseid, $sectionreturn = 0) {
    global $OUTPUT, $USER;
    $types = [];
    $preconfiguredtypes = \mod_lti\local\types_helper::get_lti_types_by_course($courseid, $USER->id,
        [LTI_COURSEVISIBLE_ACTIVITYCHOOSER]);

    foreach ($preconfiguredtypes as $ltitype) {
        $type           = new stdClass();
        $type->id       = $ltitype->id;
        $type->modclass = MOD_CLASS_ACTIVITY;
        $type->name     = 'lti_type_' . $ltitype->id;
        // Clean the name. We don't want tags here.
        $type->title    = clean_param($ltitype->name, PARAM_NOTAGS);
        $trimmeddescription = trim($ltitype->description ?? '');
        if ($trimmeddescription != '') {
            // Clean the description. We don't want tags here.
            $type->help     = clean_param($trimmeddescription, PARAM_NOTAGS);
            $type->helplink = get_string('modulename_shortcut_link', 'lti');
        }

        $iconurl = get_tool_type_icon_url($ltitype);
        $iconclass = '';
        if ($iconurl !== $OUTPUT->image_url('monologo', 'lti')->out()) {
            // Do not filter the icon if it is not the default LTI activity icon.
            $iconclass = 'nofilter';
        }
        $type->icon = html_writer::empty_tag('img', ['src' => $iconurl, 'alt' => '', 'class' => "icon $iconclass"]);

        $params = [
            'add' => 'lti',
            'return' => 0,
            'course' => $courseid,
            'typeid' => $ltitype->id,
        ];
        if (!is_null($sectionreturn)) {
            $params['sr'] = $sectionreturn;
        }
        $type->link = new moodle_url('/course/modedit.php', $params);
        $types[] = $type;
    }
    return $types;
}

function lti_get_domain_from_url($url) {
    $matches = array();

    if (preg_match(LTI_URL_DOMAIN_REGEX, $url ?? '', $matches)) {
        return $matches[1];
    }
}

function lti_get_tool_by_url_match($url, $courseid = null, $state = LTI_TOOL_STATE_CONFIGURED) {
    $possibletools = lti_get_tools_by_url($url, $state, $courseid);

    return lti_get_best_tool_by_url($url, $possibletools, $courseid);
}

function lti_get_url_thumbprint($url) {
    // Parse URL requires a schema otherwise everything goes into 'path'.  Fixed 5.4.7 or later.
    if (preg_match('/https?:\/\//', $url) !== 1) {
        $url = 'http://'.$url;
    }
    $urlparts = parse_url(strtolower($url));
    if (!isset($urlparts['path'])) {
        $urlparts['path'] = '';
    }

    if (!isset($urlparts['query'])) {
        $urlparts['query'] = '';
    }

    if (!isset($urlparts['host'])) {
        $urlparts['host'] = '';
    }

    if (substr($urlparts['host'], 0, 4) === 'www.') {
        $urlparts['host'] = substr($urlparts['host'], 4);
    }

    $urllower = $urlparts['host'] . '/' . $urlparts['path'];

    if ($urlparts['query'] != '') {
        $urllower .= '?' . $urlparts['query'];
    }

    return $urllower;
}

function lti_get_best_tool_by_url($url, $tools, $courseid = null) {
    if (count($tools) === 0) {
        return null;
    }

    $urllower = lti_get_url_thumbprint($url);

    foreach ($tools as $tool) {
        $tool->_matchscore = 0;

        $toolbaseurllower = lti_get_url_thumbprint($tool->baseurl);

        if ($urllower === $toolbaseurllower) {
            // 100 points for exact thumbprint match.
            $tool->_matchscore += 100;
        } else if (substr($urllower, 0, strlen($toolbaseurllower)) === $toolbaseurllower) {
            // 50 points if tool thumbprint starts with the base URL thumbprint.
            $tool->_matchscore += 50;
        }

        // Prefer course tools over site tools.
        if (!empty($courseid)) {
            // Minus 10 points for not matching the course id (global tools).
            if ($tool->course != $courseid) {
                $tool->_matchscore -= 10;
            }
        }
    }

    $bestmatch = array_reduce($tools, function($value, $tool) {
        if ($tool->_matchscore > $value->_matchscore) {
            return $tool;
        } else {
            return $value;
        }

    }, (object)array('_matchscore' => -1));

    // None of the tools are suitable for this URL.
    if ($bestmatch->_matchscore <= 0) {
        return null;
    }

    return $bestmatch;
}

function lti_get_shared_secrets_by_key($key) {
    global $DB;

    // Look up the shared secret for the specified key in both the types_config table (for configured tools)
    // And in the lti resource table for ad-hoc tools.
    $lti13 = LTI_VERSION_1P3;
    $query = "SELECT " . $DB->sql_compare_text('t2.value', 256) . " AS value
                FROM {lti_types_config} t1
                JOIN {lti_types_config} t2 ON t1.typeid = t2.typeid
                JOIN {lti_types} type ON t2.typeid = type.id
              WHERE t1.name = 'resourcekey'
                AND " . $DB->sql_compare_text('t1.value', 256) . " = :key1
                AND t2.name = 'password'
                AND type.state = :configured1
                AND type.ltiversion <> :ltiversion
               UNION
              SELECT tp.secret AS value
                FROM {lti_tool_proxies} tp
                JOIN {lti_types} t ON tp.id = t.toolproxyid
              WHERE tp.guid = :key2
                AND t.state = :configured2
               UNION
              SELECT password AS value
               FROM {lti}
              WHERE resourcekey = :key3";

    $sharedsecrets = $DB->get_records_sql($query, array('configured1' => LTI_TOOL_STATE_CONFIGURED, 'ltiversion' => $lti13,
        'configured2' => LTI_TOOL_STATE_CONFIGURED, 'key1' => $key, 'key2' => $key, 'key3' => $key));

    $values = array_map(function($item) {
        return $item->value;
    }, $sharedsecrets);

    // There should really only be one shared secret per key. But, we can't prevent
    // more than one getting entered. For instance, if the same key is used for two tool providers.
    return $values;
}

/**
 * Delete a Basic LTI configuration
 *
 * @param int $id   Configuration id
 */
function lti_delete_type($id) {
    global $DB;

    // We should probably just copy the launch URL to the tool instances in this case... using a single query.
    /*
    $instances = $DB->get_records('lti', array('typeid' => $id));
    foreach ($instances as $instance) {
        $instance->typeid = 0;
        $DB->update_record('lti', $instance);
    }*/

    $DB->delete_records('lti_types', array('id' => $id));
    $DB->delete_records('lti_types_config', array('typeid' => $id));
    $DB->delete_records('lti_types_categories', array('typeid' => $id));
}

function lti_set_state_for_type($id, $state) {
    global $DB;

    $DB->update_record('lti_types', (object)array('id' => $id, 'state' => $state));
}

/**
 * Transforms a basic LTI object to an array
 *
 * @param object $ltiobject    Basic LTI object
 *
 * @return array Basic LTI configuration details
 */
function lti_get_config($ltiobject) {
    $typeconfig = (array)$ltiobject;
    $additionalconfig = lti_get_type_config($ltiobject->typeid);
    $typeconfig = array_merge($typeconfig, $additionalconfig);
    return $typeconfig;
}

/**
 *
 * Generates some of the tool configuration based on the instance details
 *
 * @param int $id
 *
 * @return object configuration
 *
 */
function lti_get_type_config_from_instance($id) {
    global $DB;

    $instance = $DB->get_record('lti', array('id' => $id));
    $config = lti_get_config($instance);

    $type = new \stdClass();
    $type->lti_fix = $id;
    if (isset($config['toolurl'])) {
        $type->lti_toolurl = $config['toolurl'];
    }
    if (isset($config['instructorchoicesendname'])) {
        $type->lti_sendname = $config['instructorchoicesendname'];
    }
    if (isset($config['instructorchoicesendemailaddr'])) {
        $type->lti_sendemailaddr = $config['instructorchoicesendemailaddr'];
    }
    if (isset($config['instructorchoiceacceptgrades'])) {
        $type->lti_acceptgrades = $config['instructorchoiceacceptgrades'];
    }
    if (isset($config['instructorchoiceallowroster'])) {
        $type->lti_allowroster = $config['instructorchoiceallowroster'];
    }

    if (isset($config['instructorcustomparameters'])) {
        $type->lti_allowsetting = $config['instructorcustomparameters'];
    }
    return $type;
}

/**
 * Generates some of the tool configuration based on the admin configuration details
 *
 * @param int $id
 *
 * @return stdClass Configuration details
 */
function lti_get_type_type_config($id) {
    global $DB;

    $basicltitype = $DB->get_record('lti_types', array('id' => $id));
    $config = lti_get_type_config($id);

    $type = new \stdClass();

    $type->lti_typename = $basicltitype->name;

    $type->typeid = $basicltitype->id;

    $type->course = $basicltitype->course;

    $type->toolproxyid = $basicltitype->toolproxyid;

    $type->lti_toolurl = $basicltitype->baseurl;

    $type->lti_ltiversion = $basicltitype->ltiversion;

    $type->lti_clientid = $basicltitype->clientid;
    $type->lti_clientid_disabled = $type->lti_clientid;

    $type->lti_description = $basicltitype->description;

    $type->lti_parameters = $basicltitype->parameter;

    $type->lti_icon = $basicltitype->icon;

    $type->lti_secureicon = $basicltitype->secureicon;

    if (isset($config['resourcekey'])) {
        $type->lti_resourcekey = $config['resourcekey'];
    }
    if (isset($config['password'])) {
        $type->lti_password = $config['password'];
    }
    if (isset($config['publickey'])) {
        $type->lti_publickey = $config['publickey'];
    }
    if (isset($config['publickeyset'])) {
        $type->lti_publickeyset = $config['publickeyset'];
    }
    if (isset($config['keytype'])) {
        $type->lti_keytype = $config['keytype'];
    }
    if (isset($config['initiatelogin'])) {
        $type->lti_initiatelogin = $config['initiatelogin'];
    }
    if (isset($config['redirectionuris'])) {
        $type->lti_redirectionuris = $config['redirectionuris'];
    }

    if (isset($config['sendname'])) {
        $type->lti_sendname = $config['sendname'];
    }
    if (isset($config['instructorchoicesendname'])) {
        $type->lti_instructorchoicesendname = $config['instructorchoicesendname'];
    }
    if (isset($config['sendemailaddr'])) {
        $type->lti_sendemailaddr = $config['sendemailaddr'];
    }
    if (isset($config['instructorchoicesendemailaddr'])) {
        $type->lti_instructorchoicesendemailaddr = $config['instructorchoicesendemailaddr'];
    }
    if (isset($config['acceptgrades'])) {
        $type->lti_acceptgrades = $config['acceptgrades'];
    }
    if (isset($config['instructorchoiceacceptgrades'])) {
        $type->lti_instructorchoiceacceptgrades = $config['instructorchoiceacceptgrades'];
    }
    if (isset($config['allowroster'])) {
        $type->lti_allowroster = $config['allowroster'];
    }
    if (isset($config['instructorchoiceallowroster'])) {
        $type->lti_instructorchoiceallowroster = $config['instructorchoiceallowroster'];
    }

    if (isset($config['customparameters'])) {
        $type->lti_customparameters = $config['customparameters'];
    }

    if (isset($config['forcessl'])) {
        $type->lti_forcessl = $config['forcessl'];
    }

    if (isset($config['organizationid_default'])) {
        $type->lti_organizationid_default = $config['organizationid_default'];
    } else {
        // Tool was configured before this option was available and the default then was host.
        $type->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEHOST;
    }
    if (isset($config['organizationid'])) {
        $type->lti_organizationid = $config['organizationid'];
    }
    if (isset($config['organizationurl'])) {
        $type->lti_organizationurl = $config['organizationurl'];
    }
    if (isset($config['organizationdescr'])) {
        $type->lti_organizationdescr = $config['organizationdescr'];
    }
    if (isset($config['launchcontainer'])) {
        $type->lti_launchcontainer = $config['launchcontainer'];
    }

    if (isset($config['coursevisible'])) {
        $type->lti_coursevisible = $config['coursevisible'];
    }

    if (isset($config['contentitem'])) {
        $type->lti_contentitem = $config['contentitem'];
    }

    if (isset($config['toolurl_ContentItemSelectionRequest'])) {
        $type->lti_toolurl_ContentItemSelectionRequest = $config['toolurl_ContentItemSelectionRequest'];
    }

    if (isset($config['debuglaunch'])) {
        $type->lti_debuglaunch = $config['debuglaunch'];
    }

    if (isset($config['module_class_type'])) {
        $type->lti_module_class_type = $config['module_class_type'];
    }

    // Get the parameters from the LTI services.
    foreach ($config as $name => $value) {
        if (strpos($name, 'ltiservice_') === 0) {
            $type->{$name} = $config[$name];
        }
    }

    return $type;
}

function lti_prepare_type_for_save($type, $config) {
    if (isset($config->lti_toolurl)) {
        $type->baseurl = $config->lti_toolurl;
        if (isset($config->lti_tooldomain)) {
            $type->tooldomain = $config->lti_tooldomain;
        } else {
            $type->tooldomain = lti_get_domain_from_url($config->lti_toolurl);
        }
    }
    if (isset($config->lti_description)) {
        $type->description = $config->lti_description;
    }
    if (isset($config->lti_typename)) {
        $type->name = $config->lti_typename;
    }
    if (isset($config->lti_ltiversion)) {
        $type->ltiversion = $config->lti_ltiversion;
    }
    if (isset($config->lti_clientid)) {
        $type->clientid = $config->lti_clientid;
    }
    if ((!empty($type->ltiversion) && $type->ltiversion === LTI_VERSION_1P3) && empty($type->clientid)) {
        $type->clientid = registration_helper::get()->new_clientid();
    } else if (empty($type->clientid)) {
        $type->clientid = null;
    }
    if (isset($config->lti_coursevisible)) {
        $type->coursevisible = $config->lti_coursevisible;
    }

    if (isset($config->lti_icon)) {
        $type->icon = $config->lti_icon;
    }
    if (isset($config->lti_secureicon)) {
        $type->secureicon = $config->lti_secureicon;
    }

    $type->forcessl = !empty($config->lti_forcessl) ? $config->lti_forcessl : 0;
    $config->lti_forcessl = $type->forcessl;
    if (isset($config->lti_contentitem)) {
        $type->contentitem = !empty($config->lti_contentitem) ? $config->lti_contentitem : 0;
        $config->lti_contentitem = $type->contentitem;
    }
    if (isset($config->lti_toolurl_ContentItemSelectionRequest)) {
        if (!empty($config->lti_toolurl_ContentItemSelectionRequest)) {
            $type->toolurl_ContentItemSelectionRequest = $config->lti_toolurl_ContentItemSelectionRequest;
        } else {
            $type->toolurl_ContentItemSelectionRequest = '';
        }
        $config->lti_toolurl_ContentItemSelectionRequest = $type->toolurl_ContentItemSelectionRequest;
    }

    $type->timemodified = time();

    unset ($config->lti_typename);
    unset ($config->lti_toolurl);
    unset ($config->lti_description);
    unset ($config->lti_ltiversion);
    unset ($config->lti_clientid);
    unset ($config->lti_icon);
    unset ($config->lti_secureicon);
}

function lti_update_type($type, $config) {
    global $DB, $CFG;

    lti_prepare_type_for_save($type, $config);

    if (lti_request_is_using_ssl() && !empty($type->secureicon)) {
        $clearcache = !isset($config->oldicon) || ($config->oldicon !== $type->secureicon);
    } else {
        $clearcache = isset($type->icon) && (!isset($config->oldicon) || ($config->oldicon !== $type->icon));
    }
    unset($config->oldicon);

    if ($DB->update_record('lti_types', $type)) {
        foreach ($config as $key => $value) {
            if (substr($key, 0, 4) == 'lti_' && !is_null($value)) {
                $record = new \StdClass();
                $record->typeid = $type->id;
                $record->name = substr($key, 4);
                $record->value = $value;
                lti_update_config($record);
            }
            if (substr($key, 0, 11) == 'ltiservice_' && !is_null($value)) {
                $record = new \StdClass();
                $record->typeid = $type->id;
                $record->name = $key;
                $record->value = $value;
                lti_update_config($record);
            }
        }
        if (isset($type->toolproxyid) && $type->ltiversion === LTI_VERSION_1P3) {
            // We need to remove the tool proxy for this tool to function under 1.3.
            $toolproxyid = $type->toolproxyid;
            $DB->delete_records('lti_tool_settings', array('toolproxyid' => $toolproxyid));
            $DB->delete_records('lti_tool_proxies', array('id' => $toolproxyid));
            $type->toolproxyid = null;
            $DB->update_record('lti_types', $type);
        }
        $DB->delete_records('lti_types_categories', ['typeid' => $type->id]);
        if (isset($config->lti_coursecategories) && !empty($config->lti_coursecategories)) {
            lti_type_add_categories($type->id, $config->lti_coursecategories);
        }
        require_once($CFG->libdir.'/modinfolib.php');
        if ($clearcache) {
            $sql = "SELECT cm.id, cm.course
                      FROM {course_modules} cm
                      JOIN {modules} m ON cm.module = m.id
                      JOIN {lti} l ON l.course = cm.course
                     WHERE m.name = :name AND l.typeid = :typeid";

            $rs = $DB->get_recordset_sql($sql, ['name' => 'lti', 'typeid' => $type->id]);

            $courseids = [];
            foreach ($rs as $record) {
                $courseids[] = $record->course;
                \course_modinfo::purge_course_module_cache($record->course, $record->id);
            }
            $rs->close();
            $courseids = array_unique($courseids);
            foreach ($courseids as $courseid) {
                rebuild_course_cache($courseid, false, true);
            }
        }
    }
}

/**
 * Add LTI Type course category.
 *
 * @param int $typeid
 * @param string $lticoursecategories Comma separated list of course categories.
 * @return void
 */
function lti_type_add_categories(int $typeid, string $lticoursecategories = ''): void {
    global $DB;
    $coursecategories = explode(',', $lticoursecategories);
    foreach ($coursecategories as $coursecategory) {
        $DB->insert_record('lti_types_categories', ['typeid' => $typeid, 'categoryid' => $coursecategory]);
    }
}

function lti_add_type($type, $config) {
    global $USER, $SITE, $DB;

    lti_prepare_type_for_save($type, $config);

    if (!isset($type->state)) {
        $type->state = LTI_TOOL_STATE_PENDING;
    }

    if (!isset($type->ltiversion)) {
        $type->ltiversion = LTI_VERSION_1;
    }

    if (!isset($type->timecreated)) {
        $type->timecreated = time();
    }

    if (!isset($type->createdby)) {
        $type->createdby = $USER->id;
    }

    if (!isset($type->course)) {
        $type->course = $SITE->id;
    }

    // Create a salt value to be used for signing passed data to extension services
    // The outcome service uses the service salt on the instance. This can be used
    // for communication with services not related to a specific LTI instance.
    $config->lti_servicesalt = uniqid('', true);

    $id = $DB->insert_record('lti_types', $type);

    if ($id) {
        foreach ($config as $key => $value) {
            if (!is_null($value)) {
                if (substr($key, 0, 4) === 'lti_') {
                    $fieldname = substr($key, 4);
                } else if (substr($key, 0, 11) !== 'ltiservice_') {
                    continue;
                } else {
                    $fieldname = $key;
                }

                $record = new \StdClass();
                $record->typeid = $id;
                $record->name = $fieldname;
                $record->value = $value;

                lti_add_config($record);
            }
        }
        if (isset($config->lti_coursecategories) && !empty($config->lti_coursecategories)) {
            lti_type_add_categories($id, $config->lti_coursecategories);
        }
    }

    return $id;
}

/**
 * Given an array of tool proxies, filter them based on their state
 *
 * @param array $toolproxies An array of lti_tool_proxies records
 * @param int $state One of the LTI_TOOL_PROXY_STATE_* constants
 *
 * @return array
 */
function lti_filter_tool_proxy_types(array $toolproxies, $state) {
    $return = array();
    foreach ($toolproxies as $key => $toolproxy) {
        if ($toolproxy->state == $state) {
            $return[$key] = $toolproxy;
        }
    }
    return $return;
}

/**
 * Get the tool proxy instance given its GUID
 *
 * @param string  $toolproxyguid   Tool proxy GUID value
 *
 * @return object
 */
function lti_get_tool_proxy_from_guid($toolproxyguid) {
    global $DB;

    $toolproxy = $DB->get_record('lti_tool_proxies', array('guid' => $toolproxyguid));

    return $toolproxy;
}

/**
 * Get the tool proxy instance given its registration URL
 *
 * @param string $regurl Tool proxy registration URL
 *
 * @return array The record of the tool proxy with this url
 */
function lti_get_tool_proxies_from_registration_url($regurl) {
    global $DB;

    return $DB->get_records_sql(
        'SELECT * FROM {lti_tool_proxies}
        WHERE '.$DB->sql_compare_text('regurl', 256).' = :regurl',
        array('regurl' => $regurl)
    );
}

/**
 * Generates some of the tool proxy configuration based on the admin configuration details
 *
 * @param int $id
 *
 * @return mixed Tool Proxy details
 */
function lti_get_tool_proxy($id) {
    global $DB;

    $toolproxy = $DB->get_record('lti_tool_proxies', array('id' => $id));
    return $toolproxy;
}

/**
 * Returns lti tool proxies.
 *
 * @param bool $orphanedonly Only retrieves tool proxies that have no type associated with them
 * @return array of basicLTI types
 */
function lti_get_tool_proxies($orphanedonly) {
    global $DB;

    if ($orphanedonly) {
        $usedproxyids = array_values($DB->get_fieldset_select('lti_types', 'toolproxyid', 'toolproxyid IS NOT NULL'));
        $proxies = $DB->get_records('lti_tool_proxies', null, 'state DESC, timemodified DESC');
        foreach ($proxies as $key => $value) {
            if (in_array($value->id, $usedproxyids)) {
                unset($proxies[$key]);
            }
        }
        return $proxies;
    } else {
        return $DB->get_records('lti_tool_proxies', null, 'state DESC, timemodified DESC');
    }
}

/**
 * Generates some of the tool proxy configuration based on the admin configuration details
 *
 * @param int $id
 *
 * @return mixed  Tool Proxy details
 */
function lti_get_tool_proxy_config($id) {
    $toolproxy = lti_get_tool_proxy($id);

    $tp = new \stdClass();
    $tp->lti_registrationname = $toolproxy->name;
    $tp->toolproxyid = $toolproxy->id;
    $tp->state = $toolproxy->state;
    $tp->lti_registrationurl = $toolproxy->regurl;
    $tp->lti_capabilities = explode("\n", $toolproxy->capabilityoffered);
    $tp->lti_services = explode("\n", $toolproxy->serviceoffered);

    return $tp;
}

/**
 * Update the database with a tool proxy instance
 *
 * @param object   $config    Tool proxy definition
 *
 * @return int  Record id number
 */
function lti_add_tool_proxy($config) {
    global $USER, $DB;

    $toolproxy = new \stdClass();
    if (isset($config->lti_registrationname)) {
        $toolproxy->name = trim($config->lti_registrationname);
    }
    if (isset($config->lti_registrationurl)) {
        $toolproxy->regurl = trim($config->lti_registrationurl);
    }
    if (isset($config->lti_capabilities)) {
        $toolproxy->capabilityoffered = implode("\n", $config->lti_capabilities);
    } else {
        $toolproxy->capabilityoffered = implode("\n", array_keys(lti_get_capabilities()));
    }
    if (isset($config->lti_services)) {
        $toolproxy->serviceoffered = implode("\n", $config->lti_services);
    } else {
        $func = function($s) {
            return $s->get_id();
        };
        $servicenames = array_map($func, lti_get_services());
        $toolproxy->serviceoffered = implode("\n", $servicenames);
    }
    if (isset($config->toolproxyid) && !empty($config->toolproxyid)) {
        $toolproxy->id = $config->toolproxyid;
        if (!isset($toolproxy->state) || ($toolproxy->state != LTI_TOOL_PROXY_STATE_ACCEPTED)) {
            $toolproxy->state = LTI_TOOL_PROXY_STATE_CONFIGURED;
            $toolproxy->guid = random_string();
            $toolproxy->secret = random_string();
        }
        $id = lti_update_tool_proxy($toolproxy);
    } else {
        $toolproxy->state = LTI_TOOL_PROXY_STATE_CONFIGURED;
        $toolproxy->timemodified = time();
        $toolproxy->timecreated = $toolproxy->timemodified;
        if (!isset($toolproxy->createdby)) {
            $toolproxy->createdby = $USER->id;
        }
        $toolproxy->guid = random_string();
        $toolproxy->secret = random_string();
        $id = $DB->insert_record('lti_tool_proxies', $toolproxy);
    }

    return $id;
}

/**
 * Updates a tool proxy in the database
 *
 * @param object  $toolproxy   Tool proxy
 *
 * @return int    Record id number
 */
function lti_update_tool_proxy($toolproxy) {
    global $DB;

    $toolproxy->timemodified = time();
    $id = $DB->update_record('lti_tool_proxies', $toolproxy);

    return $id;
}

/**
 * Delete a Tool Proxy
 *
 * @param int $id   Tool Proxy id
 */
function lti_delete_tool_proxy($id) {
    global $DB;
    $DB->delete_records('lti_tool_settings', array('toolproxyid' => $id));
    $tools = $DB->get_records('lti_types', array('toolproxyid' => $id));
    foreach ($tools as $tool) {
        lti_delete_type($tool->id);
    }
    $DB->delete_records('lti_tool_proxies', array('id' => $id));
}

/**
 * Get both LTI tool proxies and tool types.
 *
 * If limit and offset are not zero, a subset of the tools will be returned. Tool proxies will be counted before tool
 * types.
 * For example: If 10 tool proxies and 10 tool types exist, and the limit is set to 15, then 10 proxies and 5 types
 * will be returned.
 *
 * @param int $limit Maximum number of tools returned.
 * @param int $offset Do not return tools before offset index.
 * @param bool $orphanedonly If true, only return orphaned proxies.
 * @param int $toolproxyid If not 0, only return tool types that have this tool proxy id.
 * @return array list(proxies[], types[]) List containing array of tool proxies and array of tool types.
 */
function lti_get_lti_types_and_proxies(int $limit = 0, int $offset = 0, bool $orphanedonly = false, int $toolproxyid = 0): array {
    global $DB;

    if ($orphanedonly) {
        $orphanedproxiessql = helper::get_tool_proxy_sql($orphanedonly, false);
        $countsql = helper::get_tool_proxy_sql($orphanedonly, true);
        $proxies  = $DB->get_records_sql($orphanedproxiessql, null, $offset, $limit);
        $totalproxiescount = $DB->count_records_sql($countsql);
    } else {
        $proxies = $DB->get_records('lti_tool_proxies', null, 'name ASC, state DESC, timemodified DESC',
            '*', $offset, $limit);
        $totalproxiescount = $DB->count_records('lti_tool_proxies');
    }

    // Find new offset and limit for tool types after getting proxies and set up query.
    $typesoffset = max($offset - $totalproxiescount, 0); // Set to 0 if negative.
    $typeslimit = max($limit - count($proxies), 0); // Set to 0 if negative.
    $typesparams = [];
    if (!empty($toolproxyid)) {
        $typesparams['toolproxyid'] = $toolproxyid;
    }

    $types = $DB->get_records('lti_types', $typesparams, 'name ASC, state DESC, timemodified DESC',
            '*', $typesoffset, $typeslimit);

    return [$proxies, array_map('serialise_tool_type', $types)];
}

/**
 * Get the total number of LTI tool types and tool proxies.
 *
 * @param bool $orphanedonly If true, only count orphaned proxies.
 * @param int $toolproxyid If not 0, only count tool types that have this tool proxy id.
 * @return int Count of tools.
 */
function lti_get_lti_types_and_proxies_count(bool $orphanedonly = false, int $toolproxyid = 0): int {
    global $DB;

    $typessql = "SELECT count(*)
                   FROM {lti_types}";
    $typesparams = [];
    if (!empty($toolproxyid)) {
        $typessql .= " WHERE toolproxyid = :toolproxyid";
        $typesparams['toolproxyid'] = $toolproxyid;
    }

    $proxiessql = helper::get_tool_proxy_sql($orphanedonly, true);

    $countsql = "SELECT ($typessql) + ($proxiessql) as total" . $DB->sql_null_from_clause();

    return $DB->count_records_sql($countsql, $typesparams);
}

/**
 * Add a tool configuration in the database
 *
 * @param object $config   Tool configuration
 *
 * @return int Record id number
 */
function lti_add_config($config) {
    global $DB;

    return $DB->insert_record('lti_types_config', $config);
}

/**
 * Updates a tool configuration in the database
 *
 * @param object  $config   Tool configuration
 *
 * @return mixed Record id number
 */
function lti_update_config($config) {
    global $DB;

    $old = $DB->get_record('lti_types_config', array('typeid' => $config->typeid, 'name' => $config->name));

    if ($old) {
        $config->id = $old->id;
        $return = $DB->update_record('lti_types_config', $config);
    } else {
        $return = $DB->insert_record('lti_types_config', $config);
    }
    return $return;
}

/**
 * Gets the tool settings
 *
 * @param int  $toolproxyid   Id of tool proxy record (or tool ID if negative)
 * @param int  $courseid      Id of course (null if system settings)
 * @param int  $instanceid    Id of course module (null if system or context settings)
 *
 * @return array  Array settings
 */
function lti_get_tool_settings($toolproxyid, $courseid = null, $instanceid = null) {
    global $DB;

    $settings = array();
    if ($toolproxyid > 0) {
        $settingsstr = $DB->get_field('lti_tool_settings', 'settings', array('toolproxyid' => $toolproxyid,
            'course' => $courseid, 'coursemoduleid' => $instanceid));
    } else {
        $settingsstr = $DB->get_field('lti_tool_settings', 'settings', array('typeid' => -$toolproxyid,
            'course' => $courseid, 'coursemoduleid' => $instanceid));
    }
    if ($settingsstr !== false) {
        $settings = json_decode($settingsstr, true);
    }
    return $settings;
}

/**
 * Sets the tool settings (
 *
 * @param array  $settings      Array of settings
 * @param int    $toolproxyid   Id of tool proxy record (or tool ID if negative)
 * @param int    $courseid      Id of course (null if system settings)
 * @param int    $instanceid    Id of course module (null if system or context settings)
 */
function lti_set_tool_settings($settings, $toolproxyid, $courseid = null, $instanceid = null) {
    global $DB;

    $json = json_encode($settings);
    if ($toolproxyid >= 0) {
        $record = $DB->get_record('lti_tool_settings', array('toolproxyid' => $toolproxyid,
            'course' => $courseid, 'coursemoduleid' => $instanceid));
    } else {
        $record = $DB->get_record('lti_tool_settings', array('typeid' => -$toolproxyid,
            'course' => $courseid, 'coursemoduleid' => $instanceid));
    }
    if ($record !== false) {
        $DB->update_record('lti_tool_settings', (object)array('id' => $record->id, 'settings' => $json, 'timemodified' => time()));
    } else {
        $record = new \stdClass();
        if ($toolproxyid > 0) {
            $record->toolproxyid = $toolproxyid;
        } else {
            $record->typeid = -$toolproxyid;
        }
        $record->course = $courseid;
        $record->coursemoduleid = $instanceid;
        $record->settings = $json;
        $record->timecreated = time();
        $record->timemodified = $record->timecreated;
        $DB->insert_record('lti_tool_settings', $record);
    }
}

/**
 * Signs the petition to launch the external tool using OAuth
 *
 * @param array  $oldparms     Parameters to be passed for signing
 * @param string $endpoint     url of the external tool
 * @param string $method       Method for sending the parameters (e.g. POST)
 * @param string $oauthconsumerkey
 * @param string $oauthconsumersecret
 * @return array|null
 */
function lti_sign_parameters($oldparms, $endpoint, $method, $oauthconsumerkey, $oauthconsumersecret) {

    $parms = $oldparms;

    $testtoken = '';

    // TODO: Switch to core oauthlib once implemented - MDL-30149.
    $hmacmethod = new lti\OAuthSignatureMethod_HMAC_SHA1();
    $testconsumer = new lti\OAuthConsumer($oauthconsumerkey, $oauthconsumersecret, null);
    $accreq = lti\OAuthRequest::from_consumer_and_token($testconsumer, $testtoken, $method, $endpoint, $parms);
    $accreq->sign_request($hmacmethod, $testconsumer, $testtoken);

    $newparms = $accreq->get_parameters();

    return $newparms;
}

/**
 * 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($parms, $endpoint, $oauthconsumerkey, $typeid = 0, $nonce = '') {
    global $CFG;

    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();
    $payload = array(
        'nonce' => $nonce,
        'iat' => $now,
        'exp' => $now + 60,
    );
    $payload['iss'] = $CFG->wwwroot;
    $payload['aud'] = $oauthconsumerkey;
    $payload[LTI_JWT_CLAIM_PREFIX . '/claim/deployment_id'] = strval($typeid);
    $payload[LTI_JWT_CLAIM_PREFIX . '/claim/target_link_uri'] = $endpoint;

    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 = jwks_helper::get_private_key();
    $jwt = JWT::encode($payload, $privatekey['key'], 'RS256', $privatekey['kid']);

    $newparms = array();
    $newparms['id_token'] = $jwt;

    return $newparms;
}

/**
 * Verfies the JWT and converts its claims to their equivalent message parameter.
 *
 * @param int    $typeid
 * @param string $jwtparam   JWT parameter
 *
 * @return array  message parameters
 * @throws moodle_exception
 */
function lti_convert_from_jwt($typeid, $jwtparam) {

    $params = array();
    $parts = explode('.', $jwtparam);
    $ok = (count($parts) === 3);
    if ($ok) {
        $payload = JWT::urlsafeB64Decode($parts[1]);
        $claims = json_decode($payload, true);
        $ok = !is_null($claims) && !empty($claims['iss']);
    }
    if ($ok) {
        lti_verify_jwt_signature($typeid, $claims['iss'], $jwtparam);
        $params['oauth_consumer_key'] = $claims['iss'];
        foreach (lti_get_jwt_claim_mapping() as $key => $mapping) {
            $claim = LTI_JWT_CLAIM_PREFIX;
            if (!empty($mapping['suffix'])) {
                $claim .= "-{$mapping['suffix']}";
            }
            $claim .= '/claim/';
            if (is_null($mapping['group'])) {
                $claim = $mapping['claim'];
            } else if (empty($mapping['group'])) {
                $claim .= $mapping['claim'];
            } else {
                $claim .= $mapping['group'];
            }
            if (isset($claims[$claim])) {
                $value = null;
                if (empty($mapping['group'])) {
                    $value = $claims[$claim];
                } else {
                    $group = $claims[$claim];
                    if (is_array($group) && array_key_exists($mapping['claim'], $group)) {
                        $value = $group[$mapping['claim']];
                    }
                }
                if (!empty($value) && $mapping['isarray']) {
                    if (is_array($value)) {
                        if (is_array($value[0])) {
                            $value = json_encode($value);
                        } else {
                            $value = implode(',', $value);
                        }
                    }
                }
                if (!is_null($value) && is_string($value) && (strlen($value) > 0)) {
                    $params[$key] = $value;
                }
            }
            $claim = LTI_JWT_CLAIM_PREFIX . '/claim/custom';
            if (isset($claims[$claim])) {
                $custom = $claims[$claim];
                if (is_array($custom)) {
                    foreach ($custom as $key => $value) {
                        $params["custom_{$key}"] = $value;
                    }
                }
            }
            $claim = LTI_JWT_CLAIM_PREFIX . '/claim/ext';
            if (isset($claims[$claim])) {
                $ext = $claims[$claim];
                if (is_array($ext)) {
                    foreach ($ext as $key => $value) {
                        $params["ext_{$key}"] = $value;
                    }
                }
            }
        }
    }
    if (isset($params['content_items'])) {
        $params['content_items'] = lti_convert_content_items($params['content_items']);
    }
    $messagetypemapping = lti_get_jwt_message_type_mapping();
    if (isset($params['lti_message_type']) && array_key_exists($params['lti_message_type'], $messagetypemapping)) {
        $params['lti_message_type'] = $messagetypemapping[$params['lti_message_type']];
    }
    return $params;
}

/**
 * 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
 */
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;
}

/**
 * Generate the form for initiating a login request for an LTI 1.3 message
 *
 * @param int            $courseid  Course ID
 * @param int            $cmid        LTI instance ID
 * @param stdClass|null  $instance  LTI instance
 * @param stdClass       $config    Tool type configuration
 * @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($courseid, $cmid, $instance, $config, $messagetype = 'basic-lti-launch-request',
        $title = '', $text = '', $foruserid = 0) {
    global $SESSION;

    $params = lti_build_login_request($courseid, $cmid, $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            $cmid        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($courseid, $cmid, $instance, $config, $messagetype, $foruserid=0, $title = '', $text = '') {
    global $USER, $CFG, $SESSION;
    $ltihint = [];
    if (!empty($instance)) {
        $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $config->lti_toolurl;
        $launchid = 'ltilaunch'.$instance->id.'_'.rand();
        $ltihint['cmid'] = $cmid;
        $SESSION->$launchid = "{$courseid},{$config->typeid},{$cmid},{$messagetype},{$foruserid},,";
    } else {
        $endpoint = $config->lti_toolurl;
        if (($messagetype === 'ContentItemSelectionRequest') && !empty($config->lti_toolurl_ContentItemSelectionRequest)) {
            $endpoint = $config->lti_toolurl_ContentItemSelectionRequest;
        }
        $launchid = "ltilaunch_$messagetype".rand();
        $SESSION->$launchid =
            "{$courseid},{$config->typeid},,{$messagetype},{$foruserid}," . base64_encode($title) . ',' . base64_encode($text);
    }
    $endpoint = trim($endpoint);
    $services = lti_get_services();
    foreach ($services as $service) {
        [$endpoint] = $service->override_endpoint($messagetype ?? 'basic-lti-launch-request', $endpoint, '', $courseid, $instance);
    }

    $ltihint['launchid'] = $launchid;
    // 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'] = $CFG->wwwroot;
    $params['target_link_uri'] = $endpoint;
    $params['login_hint'] = $USER->id;
    $params['lti_message_hint'] = json_encode($ltihint);
    $params['client_id'] = $config->lti_clientid;
    $params['lti_deployment_id'] = $config->typeid;
    return $params;
}

function lti_get_type($typeid) {
    global $DB;

    return $DB->get_record('lti_types', array('id' => $typeid));
}

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;
    }

    $devicetype = core_useragent::get_device_type();

    // Scrolling within the object element doesn't work on iOS or Android
    // Opening the popup window also had some issues in testing
    // For mobile devices, always take up the entire screen to ensure the best experience.
    if ($devicetype === core_useragent::DEVICETYPE_MOBILE || $devicetype === core_useragent::DEVICETYPE_TABLET ) {
        $launchcontainer = LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW;
    }

    return $launchcontainer;
}

function lti_request_is_using_ssl() {
    global $CFG;
    return (stripos($CFG->wwwroot, 'https://') === 0);
}

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;
}

/**
 * Determines if we should try to log the request
 *
 * @param string $rawbody
 * @return bool
 */
function lti_should_log_request($rawbody) {
    global $CFG;

    if (empty($CFG->mod_lti_log_users)) {
        return false;
    }

    $logusers = explode(',', $CFG->mod_lti_log_users);
    if (empty($logusers)) {
        return false;
    }

    try {
        $xml = new \SimpleXMLElement($rawbody);
        $ns  = $xml->getNamespaces();
        $ns  = array_shift($ns);
        $xml->registerXPathNamespace('lti', $ns);
        $requestuserid = '';
        if ($node = $xml->xpath('//lti:userId')) {
            $node = $node[0];
            $requestuserid = clean_param((string) $node, PARAM_INT);
        } else if ($node = $xml->xpath('//lti:sourcedId')) {
            $node = $node[0];
            $resultjson = json_decode((string) $node);
            $requestuserid = clean_param($resultjson->data->userid, PARAM_INT);
        }
    } catch (Exception $e) {
        return false;
    }

    if (empty($requestuserid) or !in_array($requestuserid, $logusers)) {
        return false;
    }

    return true;
}

/**
 * Logs the request to a file in temp dir.
 *
 * @param string $rawbody
 */
function lti_log_request($rawbody) {
    if ($tempdir = make_temp_directory('mod_lti', false)) {
        if ($tempfile = tempnam($tempdir, 'mod_lti_request'.date('YmdHis'))) {
            $content  = "Request Headers:\n";
            foreach (moodle\mod\lti\OAuthUtil::get_headers() as $header => $value) {
                $content .= "$header: $value\n";
            }
            $content .= "Request Body:\n";
            $content .= $rawbody;

            file_put_contents($tempfile, $content);
            chmod($tempfile, 0644);
        }
    }
}

/**
 * Log an LTI response.
 *
 * @param string $responsexml The response XML
 * @param Exception $e If there was an exception, pass that too
 */
function lti_log_response($responsexml, $e = null) {
    if ($tempdir = make_temp_directory('mod_lti', false)) {
        if ($tempfile = tempnam($tempdir, 'mod_lti_response'.date('YmdHis'))) {
            $content = '';
            if ($e instanceof Exception) {
                $info = get_exception_info($e);

                $content .= "Exception:\n";
                $content .= "Message: $info->message\n";
                $content .= "Debug info: $info->debuginfo\n";
                $content .= "Backtrace:\n";
                $content .= format_backtrace($info->backtrace, true);
                $content .= "\n";
            }
            $content .= "Response XML:\n";
            $content .= $responsexml;

            file_put_contents($tempfile, $content);
            chmod($tempfile, 0644);
        }
    }
}

/**
 * Fetches LTI type configuration for an LTI instance
 *
 * @param stdClass $instance
 * @return array Can be empty if no type is found
 */
function lti_get_type_config_by_instance($instance) {
    $typeid = null;
    if (empty($instance->typeid)) {
        $tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course);
        if ($tool) {
            $typeid = $tool->id;
        }
    } else {
        $typeid = $instance->typeid;
    }
    if (!empty($typeid)) {
        return lti_get_type_config($typeid);
    }
    return array();
}

/**
 * Enforce type config settings onto the LTI instance
 *
 * @param stdClass $instance
 * @param array $typeconfig
 */
function lti_force_type_config_settings($instance, array $typeconfig) {
    $forced = array(
        'instructorchoicesendname'      => 'sendname',
        'instructorchoicesendemailaddr' => 'sendemailaddr',
        'instructorchoiceacceptgrades'  => 'acceptgrades',
    );

    foreach ($forced as $instanceparam => $typeconfigparam) {
        if (array_key_exists($typeconfigparam, $typeconfig) && $typeconfig[$typeconfigparam] != LTI_SETTING_DELEGATE) {
            $instance->$instanceparam = $typeconfig[$typeconfigparam];
        }
    }
}

/**
 * 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;

}

/**
 * Initializes an array with the services supported by the LTI module
 *
 * @return array List of services
 */
function lti_get_services() {

    $services = array();
    $definedservices = core_component::get_plugin_list('ltiservice');
    foreach ($definedservices as $name => $location) {
        $classname = "\\ltiservice_{$name}\\local\\service\\{$name}";
        $services[] = new $classname();
    }

    return $services;

}

/**
 * Initializes an instance of the named service
 *
 * @param string $servicename Name of service
 *
 * @return bool|\mod_lti\local\ltiservice\service_base Service
 */
function lti_get_service_by_name($servicename) {

    $service = false;
    $classname = "\\ltiservice_{$servicename}\\local\\service\\{$servicename}";
    if (class_exists($classname)) {
        $service = new $classname();
    }

    return $service;

}

/**
 * Finds a service by id
 *
 * @param \mod_lti\local\ltiservice\service_base[] $services Array of services
 * @param string $resourceid  ID of resource
 *
 * @return mod_lti\local\ltiservice\service_base Service
 */
function lti_get_service_by_resource_id($services, $resourceid) {

    $service = false;
    foreach ($services as $aservice) {
        foreach ($aservice->get_resources() as $resource) {
            if ($resource->get_id() === $resourceid) {
                $service = $aservice;
                break 2;
            }
        }
    }

    return $service;

}

/**
 * Initializes an array with the scopes for services supported by the LTI module
 * and authorized for this particular tool instance.
 *
 * @param object $type  LTI tool type
 * @param array  $typeconfig  LTI tool type configuration
 *
 * @return array List of scopes
 */
function lti_get_permitted_service_scopes($type, $typeconfig) {

    $services = lti_get_services();
    $scopes = array();
    foreach ($services as $service) {
        $service->set_type($type);
        $service->set_typeconfig($typeconfig);
        $servicescopes = $service->get_permitted_scopes();
        if (!empty($servicescopes)) {
            $scopes = array_merge($scopes, $servicescopes);
        }
    }

    return $scopes;
}

/**
 * Extracts the named contexts from a tool proxy
 *
 * @param object $json
 *
 * @return array Contexts
 */
function lti_get_contexts($json) {

    $contexts = array();
    if (isset($json->{'@context'})) {
        foreach ($json->{'@context'} as $context) {
            if (is_object($context)) {
                $contexts = array_merge(get_object_vars($context), $contexts);
            }
        }
    }

    return $contexts;

}

/**
 * Converts an ID to a fully-qualified ID
 *
 * @param array $contexts
 * @param string $id
 *
 * @return string Fully-qualified ID
 */
function lti_get_fqid($contexts, $id) {

    $parts = explode(':', $id, 2);
    if (count($parts) > 1) {
        if (array_key_exists($parts[0], $contexts)) {
            $id = $contexts[$parts[0]] . $parts[1];
        }
    }

    return $id;

}

/**
 * Returns the icon for the given tool type
 *
 * @param stdClass $type The tool type
 *
 * @return string The url to the tool type's corresponding icon
 */
function get_tool_type_icon_url(stdClass $type) {
    global $OUTPUT;

    $iconurl = $type->secureicon;

    if (empty($iconurl)) {
        $iconurl = $type->icon;
    }

    if (empty($iconurl)) {
        $iconurl = $OUTPUT->image_url('monologo', 'lti')->out();
    }

    return $iconurl;
}

/**
 * Returns the edit url for the given tool type
 *
 * @param stdClass $type The tool type
 *
 * @return string The url to edit the tool type
 */
function get_tool_type_edit_url(stdClass $type) {
    $url = new moodle_url('/mod/lti/typessettings.php',
                          array('action' => 'update', 'id' => $type->id, 'sesskey' => sesskey(), 'returnto' => 'toolconfigure'));
    return $url->out();
}

/**
 * Returns the edit url for the given tool proxy.
 *
 * @param stdClass $proxy The tool proxy
 *
 * @return string The url to edit the tool type
 */
function get_tool_proxy_edit_url(stdClass $proxy) {
    $url = new moodle_url('/mod/lti/registersettings.php',
                          array('action' => 'update', 'id' => $proxy->id, 'sesskey' => sesskey(), 'returnto' => 'toolconfigure'));
    return $url->out();
}

/**
 * Returns the course url for the given tool type
 *
 * @param stdClass $type The tool type
 *
 * @return string The url to the course of the tool type, void if it is a site wide type
 */
function get_tool_type_course_url(stdClass $type) {
    if ($type->course != 1) {
        $url = new moodle_url('/course/view.php', array('id' => $type->course));
        return $url->out();
    }
    return null;
}

/**
 * Returns the icon and edit urls for the tool type and the course url if it is a course type.
 *
 * @param stdClass $type The tool type
 *
 * @return array The urls of the tool type
 */
function get_tool_type_urls(stdClass $type) {
    $courseurl = get_tool_type_course_url($type);

    $urls = array(
        'icon' => get_tool_type_icon_url($type),
        'edit' => get_tool_type_edit_url($type),
    );

    if ($courseurl) {
        $urls['course'] = $courseurl;
    }

    $url = new moodle_url('/mod/lti/certs.php');
    $urls['publickeyset'] = $url->out();
    $url = new moodle_url('/mod/lti/token.php');
    $urls['accesstoken'] = $url->out();
    $url = new moodle_url('/mod/lti/auth.php');
    $urls['authrequest'] = $url->out();

    return $urls;
}

/**
 * Returns the icon and edit urls for the tool proxy.
 *
 * @param stdClass $proxy The tool proxy
 *
 * @return array The urls of the tool proxy
 */
function get_tool_proxy_urls(stdClass $proxy) {
    global $OUTPUT;

    $urls = array(
        'icon' => $OUTPUT->image_url('monologo', 'lti')->out(),
        'edit' => get_tool_proxy_edit_url($proxy),
    );

    return $urls;
}

/**
 * Returns information on the current state of the tool type
 *
 * @param stdClass $type The tool type
 *
 * @return array An array with a text description of the state, and boolean for whether it is in each state:
 * pending, configured, rejected, unknown
 */
function get_tool_type_state_info(stdClass $type) {
    $isconfigured = false;
    $ispending = false;
    $isrejected = false;
    $isunknown = false;
    switch ($type->state) {
        case LTI_TOOL_STATE_CONFIGURED:
            $state = get_string('active', 'mod_lti');
            $isconfigured = true;
            break;
        case LTI_TOOL_STATE_PENDING:
            $state = get_string('pending', 'mod_lti');
            $ispending = true;
            break;
        case LTI_TOOL_STATE_REJECTED:
            $state = get_string('rejected', 'mod_lti');
            $isrejected = true;
            break;
        default:
            $state = get_string('unknownstate', 'mod_lti');
            $isunknown = true;
            break;
    }

    return array(
        'text' => $state,
        'pending' => $ispending,
        'configured' => $isconfigured,
        'rejected' => $isrejected,
        'unknown' => $isunknown
    );
}

/**
 * Returns information on the configuration of the tool type
 *
 * @param stdClass $type The tool type
 *
 * @return array An array with configuration details
 */
function get_tool_type_config($type) {
    global $CFG;
    $platformid = $CFG->wwwroot;
    $clientid = $type->clientid;
    $deploymentid = $type->id;
    $publickeyseturl = new moodle_url('/mod/lti/certs.php');
    $publickeyseturl = $publickeyseturl->out();

    $accesstokenurl = new moodle_url('/mod/lti/token.php');
    $accesstokenurl = $accesstokenurl->out();

    $authrequesturl = new moodle_url('/mod/lti/auth.php');
    $authrequesturl = $authrequesturl->out();

    return array(
        'platformid' => $platformid,
        'clientid' => $clientid,
        'deploymentid' => $deploymentid,
        'publickeyseturl' => $publickeyseturl,
        'accesstokenurl' => $accesstokenurl,
        'authrequesturl' => $authrequesturl
    );
}

/**
 * Returns a summary of each LTI capability this tool type requires in plain language
 *
 * @param stdClass $type The tool type
 *
 * @return array An array of text descriptions of each of the capabilities this tool type requires
 */
function get_tool_type_capability_groups($type) {
    $capabilities = lti_get_enabled_capabilities($type);
    $groups = array();
    $hascourse = false;
    $hasactivities = false;
    $hasuseraccount = false;
    $hasuserpersonal = false;

    foreach ($capabilities as $capability) {
        // Bail out early if we've already found all groups.
        if (count($groups) >= 4) {
            continue;
        }

        if (!$hascourse && preg_match('/^CourseSection/', $capability)) {
            $hascourse = true;
            $groups[] = get_string('courseinformation', 'mod_lti');
        } else if (!$hasactivities && preg_match('/^ResourceLink/', $capability)) {
            $hasactivities = true;
            $groups[] = get_string('courseactivitiesorresources', 'mod_lti');
        } else if (!$hasuseraccount && preg_match('/^User/', $capability) || preg_match('/^Membership/', $capability)) {
            $hasuseraccount = true;
            $groups[] = get_string('useraccountinformation', 'mod_lti');
        } else if (!$hasuserpersonal && preg_match('/^Person/', $capability)) {
            $hasuserpersonal = true;
            $groups[] = get_string('userpersonalinformation', 'mod_lti');
        }
    }

    return $groups;
}


/**
 * Returns the ids of each instance of this tool type
 *
 * @param stdClass $type The tool type
 *
 * @return array An array of ids of the instances of this tool type
 */
function get_tool_type_instance_ids($type) {
    global $DB;

    return array_keys($DB->get_fieldset_select('lti', 'id', 'typeid = ?', array($type->id)));
}

/**
 * Serialises this tool type
 *
 * @param stdClass $type The tool type
 *
 * @return array An array of values representing this type
 */
function serialise_tool_type(stdClass $type) {
    global $CFG;

    $capabilitygroups = get_tool_type_capability_groups($type);
    $instanceids = get_tool_type_instance_ids($type);
    // Clean the name. We don't want tags here.
    $name = clean_param($type->name, PARAM_NOTAGS);
    if (!empty($type->description)) {
        // Clean the description. We don't want tags here.
        $description = clean_param($type->description, PARAM_NOTAGS);
    } else {
        $description = get_string('editdescription', 'mod_lti');
    }
    return array(
        'id' => $type->id,
        'name' => $name,
        'description' => $description,
        'urls' => get_tool_type_urls($type),
        'state' => get_tool_type_state_info($type),
        'platformid' => $CFG->wwwroot,
        'clientid' => $type->clientid,
        'deploymentid' => $type->id,
        'hascapabilitygroups' => !empty($capabilitygroups),
        'capabilitygroups' => $capabilitygroups,
        // Course ID of 1 means it's not linked to a course.
        'courseid' => $type->course == 1 ? 0 : $type->course,
        'instanceids' => $instanceids,
        'instancecount' => count($instanceids)
    );
}

/**
 * Loads the cartridge information into the tool type, if the launch url is for a cartridge file
 *
 * @param stdClass $type The tool type object to be filled in
 * @since Moodle 3.1
 */
function lti_load_type_if_cartridge($type) {
    if (!empty($type->lti_toolurl) && lti_is_cartridge($type->lti_toolurl)) {
        lti_load_type_from_cartridge($type->lti_toolurl, $type);
    }
}

/**
 * Loads the cartridge information into the new tool, if the launch url is for a cartridge file
 *
 * @param stdClass $lti The tools config
 * @since Moodle 3.1
 */
function lti_load_tool_if_cartridge($lti) {
    if (!empty($lti->toolurl) && lti_is_cartridge($lti->toolurl)) {
        lti_load_tool_from_cartridge($lti->toolurl, $lti);
    }
}

/**
 * Determines if the given url is for a IMS basic cartridge
 *
 * @param  string $url The url to be checked
 * @return True if the url is for a cartridge
 * @since Moodle 3.1
 */
function lti_is_cartridge($url) {
    // If it is empty, it's not a cartridge.
    if (empty($url)) {
        return false;
    }
    // If it has xml at the end of the url, it's a cartridge.
    if (preg_match('/\.xml$/', $url)) {
        return true;
    }
    // Even if it doesn't have .xml, load the url to check if it's a cartridge..
    try {
        $toolinfo = lti_load_cartridge($url,
            array(
                "launch_url" => "launchurl"
            )
        );
        if (!empty($toolinfo['launchurl'])) {
            return true;
        }
    } catch (moodle_exception $e) {
        return false; // Error loading the xml, so it's not a cartridge.
    }
    return false;
}

/**
 * Allows you to load settings for an external tool type from an IMS cartridge.
 *
 * @param  string   $url     The URL to the cartridge
 * @param  stdClass $type    The tool type object to be filled in
 * @throws moodle_exception if the cartridge could not be loaded correctly
 * @since Moodle 3.1
 */
function lti_load_type_from_cartridge($url, $type) {
    $toolinfo = lti_load_cartridge($url,
        array(
            "title" => "lti_typename",
            "launch_url" => "lti_toolurl",
            "description" => "lti_description",
            "icon" => "lti_icon",
            "secure_icon" => "lti_secureicon"
        ),
        array(
            "icon_url" => "lti_extension_icon",
            "secure_icon_url" => "lti_extension_secureicon"
        )
    );
    // If an activity name exists, unset the cartridge name so we don't override it.
    if (isset($type->lti_typename)) {
        unset($toolinfo['lti_typename']);
    }

    // Always prefer cartridge core icons first, then, if none are found, look at the extension icons.
    if (empty($toolinfo['lti_icon']) && !empty($toolinfo['lti_extension_icon'])) {
        $toolinfo['lti_icon'] = $toolinfo['lti_extension_icon'];
    }
    unset($toolinfo['lti_extension_icon']);

    if (empty($toolinfo['lti_secureicon']) && !empty($toolinfo['lti_extension_secureicon'])) {
        $toolinfo['lti_secureicon'] = $toolinfo['lti_extension_secureicon'];
    }
    unset($toolinfo['lti_extension_secureicon']);

    // Ensure Custom icons aren't overridden by cartridge params.
    if (!empty($type->lti_icon)) {
        unset($toolinfo['lti_icon']);
    }

    if (!empty($type->lti_secureicon)) {
        unset($toolinfo['lti_secureicon']);
    }

    foreach ($toolinfo as $property => $value) {
        $type->$property = $value;
    }
}

/**
 * Allows you to load in the configuration for an external tool from an IMS cartridge.
 *
 * @param  string   $url    The URL to the cartridge
 * @param  stdClass $lti    LTI object
 * @throws moodle_exception if the cartridge could not be loaded correctly
 * @since Moodle 3.1
 */
function lti_load_tool_from_cartridge($url, $lti) {
    $toolinfo = lti_load_cartridge($url,
        array(
            "title" => "name",
            "launch_url" => "toolurl",
            "secure_launch_url" => "securetoolurl",
            "description" => "intro",
            "icon" => "icon",
            "secure_icon" => "secureicon"
        ),
        array(
            "icon_url" => "extension_icon",
            "secure_icon_url" => "extension_secureicon"
        )
    );
    // If an activity name exists, unset the cartridge name so we don't override it.
    if (isset($lti->name)) {
        unset($toolinfo['name']);
    }

    // Always prefer cartridge core icons first, then, if none are found, look at the extension icons.
    if (empty($toolinfo['icon']) && !empty($toolinfo['extension_icon'])) {
        $toolinfo['icon'] = $toolinfo['extension_icon'];
    }
    unset($toolinfo['extension_icon']);

    if (empty($toolinfo['secureicon']) && !empty($toolinfo['extension_secureicon'])) {
        $toolinfo['secureicon'] = $toolinfo['extension_secureicon'];
    }
    unset($toolinfo['extension_secureicon']);

    foreach ($toolinfo as $property => $value) {
        $lti->$property = $value;
    }
}

/**
 * Search for a tag within an XML DOMDocument
 *
 * @param  string $url The url of the cartridge to be loaded
 * @param  array  $map The map of tags to keys in the return array
 * @param  array  $propertiesmap The map of properties to keys in the return array
 * @return array An associative array with the given keys and their values from the cartridge
 * @throws moodle_exception if the cartridge could not be loaded correctly
 * @since Moodle 3.1
 */
function lti_load_cartridge($url, $map, $propertiesmap = array()) {
    global $CFG;
    require_once($CFG->libdir. "/filelib.php");

    $curl = new curl();
    $response = $curl->get($url);

    // Got a completely empty response (real or error), cannot process this with
    // DOMDocument::loadXML() because it errors with ValueError. So let's throw
    // the moodle_exception before waiting to examine the errors later.
    if (trim($response) === '') {
        throw new moodle_exception('errorreadingfile', '', '', $url);
    }

    // TODO MDL-46023 Replace this code with a call to the new library.
    $origerrors = libxml_use_internal_errors(true);
    libxml_clear_errors();

    $document = new DOMDocument();
    @$document->loadXML($response, LIBXML_NONET);

    $cartridge = new DomXpath($document);

    $errors = libxml_get_errors();

    libxml_clear_errors();
    libxml_use_internal_errors($origerrors);

    if (count($errors) > 0) {
        $message = 'Failed to load cartridge.';
        foreach ($errors as $error) {
            $message .= "\n" . trim($error->message, "\n\r\t .") . " at line " . $error->line;
        }
        throw new moodle_exception('errorreadingfile', '', '', $url, $message);
    }

    $toolinfo = array();
    foreach ($map as $tag => $key) {
        $value = get_tag($tag, $cartridge);
        if ($value) {
            $toolinfo[$key] = $value;
        }
    }
    if (!empty($propertiesmap)) {
        foreach ($propertiesmap as $property => $key) {
            $value = get_tag("property", $cartridge, $property);
            if ($value) {
                $toolinfo[$key] = $value;
            }
        }
    }

    return $toolinfo;
}

/**
 * Search for a tag within an XML DOMDocument
 *
 * @param  stdClass $tagname The name of the tag to search for
 * @param  XPath    $xpath   The XML to find the tag in
 * @param  XPath    $attribute The attribute to search for (if we should search for a child node with the given
 * value for the name attribute
 * @since Moodle 3.1
 */
function get_tag($tagname, $xpath, $attribute = null) {
    if ($attribute) {
        $result = $xpath->query('//*[local-name() = \'' . $tagname . '\'][@name="' . $attribute . '"]');
    } else {
        $result = $xpath->query('//*[local-name() = \'' . $tagname . '\']');
    }
    if ($result->length > 0) {
        return $result->item(0)->nodeValue;
    }
    return null;
}

/**
 * Create a new access token.
 *
 * @param int $typeid Tool type ID
 * @param string[] $scopes Scopes permitted for new token
 *
 * @return stdClass Access token
 */
function lti_new_access_token($typeid, $scopes) {
    global $DB;

    // Make sure the token doesn't exist (even if it should be almost impossible with the random generation).
    $numtries = 0;
    do {
        $numtries ++;
        $generatedtoken = md5(uniqid(rand(), 1));
        if ($numtries > 5) {
            throw new moodle_exception('Failed to generate LTI access token');
        }
    } while ($DB->record_exists('lti_access_tokens', array('token' => $generatedtoken)));
    $newtoken = new stdClass();
    $newtoken->typeid = $typeid;
    $newtoken->scope = json_encode(array_values($scopes));
    $newtoken->token = $generatedtoken;

    $newtoken->timecreated = time();
    $newtoken->validuntil = $newtoken->timecreated + LTI_ACCESS_TOKEN_LIFE;
    $newtoken->lastaccess = null;

    $DB->insert_record('lti_access_tokens', $newtoken);

    return $newtoken;

}


/**
 * Wrapper for function libxml_disable_entity_loader() deprecated in PHP 8
 *
 * Method was deprecated in PHP 8 and it shows deprecation message. However it is still
 * required in the previous versions on PHP. While Moodle supports both PHP 7 and 8 we need to keep it.
 * @see https://php.watch/versions/8.0/libxml_disable_entity_loader-deprecation
 *
 * @param bool $value
 * @return bool
 *
 * @deprecated since Moodle 4.3
 */
function lti_libxml_disable_entity_loader(bool $value): bool {
    debugging(__FUNCTION__ . '() is deprecated, please do not use it any more', DEBUG_DEVELOPER);
    return true;
}

Filemanager

Name Type Size Permission Actions
amd Folder 0777
backup Folder 0777
classes Folder 0777
db Folder 0777
lang Folder 0777
pix Folder 0777
service Folder 0777
source Folder 0777
templates Folder 0777
tests Folder 0777
OAuth.php File 29.62 KB 0777
OAuthBody.php File 5.41 KB 0777
TrivialStore.php File 4.87 KB 0777
auth.php File 6.06 KB 0777
certs.php File 1.16 KB 0777
contentitem.php File 2.43 KB 0777
contentitem_return.php File 3.75 KB 0777
coursetooledit.php File 3.23 KB 0777
coursetools.php File 2.18 KB 0777
deprecatedlib.php File 1.1 KB 0777
edit_form.php File 21.81 KB 0777
externalregistrationreturn.php File 2.31 KB 0777
grade.php File 1.36 KB 0777
index.php File 4.46 KB 0777
launch.php File 4.31 KB 0777
lib.php File 25.84 KB 0777
locallib.php File 159.29 KB 0777
mod_form.php File 29.14 KB 0777
openid-configuration.php File 2.66 KB 0777
openid-registration.php File 3.46 KB 0777
register.php File 4.37 KB 0777
register_form.php File 4.33 KB 0777
registersettings.php File 3.15 KB 0777
registration.php File 1.4 KB 0777
registrationreturn.php File 3.47 KB 0777
request_tool.php File 2.57 KB 0777
return.php File 4.63 KB 0777
service.php File 7.34 KB 0777
servicelib.php File 9.89 KB 0777
services.php File 3.01 KB 0777
settings.php File 8.03 KB 0777
startltiadvregistration.php File 2.95 KB 0777
styles.css File 9.38 KB 0777
token.php File 3.42 KB 0777
toolconfigure.php File 2.06 KB 0777
toolproxies.php File 6.37 KB 0777
toolssettings.php File 3.79 KB 0777
typessettings.php File 6.26 KB 0777
upgrade.txt File 4.91 KB 0777
upgradelib.php File 2.08 KB 0777
version.php File 2.58 KB 0777
view.php File 8.5 KB 0777
Filemanager