first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+914
View File
@@ -0,0 +1,914 @@
<?php
// 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
//
// OAuth.php is distributed under the MIT License
//
// The MIT License
//
// Copyright (c) 2007 Andy Smith
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// 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 contains the OAuth 1.0a implementation used for support for LTI 1.1.
*
* @package mod_lti
* @copyright moodle
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace moodle\mod\lti;//Using a namespace as the basicLTI module imports classes with the same names
defined('MOODLE_INTERNAL') || die;
$lastcomputedsignature = false;
/**
* Generic exception class
*/
class OAuthException extends \Exception {
// pass
}
/**
* OAuth 1.0 Consumer class
*/
class OAuthConsumer {
public $key;
public $secret;
/** @var string|null callback URL. */
public ?string $callback_url;
function __construct($key, $secret, $callback_url = null) {
$this->key = $key;
$this->secret = $secret;
$this->callback_url = $callback_url;
}
function __toString() {
return "OAuthConsumer[key=$this->key,secret=$this->secret]";
}
}
class OAuthToken {
// access tokens and request tokens
public $key;
public $secret;
/**
* key = the token
* secret = the token secret
*/
function __construct($key, $secret) {
$this->key = $key;
$this->secret = $secret;
}
/**
* generates the basic string serialization of a token that a server
* would respond to request_token and access_token calls with
*/
function to_string() {
return "oauth_token=" .
OAuthUtil::urlencode_rfc3986($this->key) .
"&oauth_token_secret=" .
OAuthUtil::urlencode_rfc3986($this->secret);
}
function __toString() {
return $this->to_string();
}
}
class OAuthSignatureMethod {
public function check_signature(&$request, $consumer, $token, $signature) {
$built = $this->build_signature($request, $consumer, $token);
return $built == $signature;
}
}
/**
* Base class for the HMac based signature methods.
*/
abstract class OAuthSignatureMethod_HMAC extends OAuthSignatureMethod {
/**
* Name of the Algorithm used.
*
* @return string algorithm name.
*/
abstract public function get_name(): string;
public function build_signature($request, $consumer, $token) {
global $lastcomputedsignature;
$lastcomputedsignature = false;
$basestring = $request->get_signature_base_string();
$request->base_string = $basestring;
$key_parts = array(
$consumer->secret,
($token) ? $token->secret : ""
);
$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
$key = implode('&', $key_parts);
$computedsignature = base64_encode(hash_hmac(strtolower(substr($this->get_name(), 5)), $basestring, $key, true));
$lastcomputedsignature = $computedsignature;
return $computedsignature;
}
}
/**
* Implementation for SHA 1.
*/
class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod_HMAC {
/**
* Name of the Algorithm used.
*
* @return string algorithm name.
*/
public function get_name(): string {
return "HMAC-SHA1";
}
}
/**
* Implementation for SHA 256.
*/
class OAuthSignatureMethod_HMAC_SHA256 extends OAuthSignatureMethod_HMAC {
/**
* Name of the Algorithm used.
*
* @return string algorithm name.
*/
public function get_name(): string {
return "HMAC-SHA256";
}
}
class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
/**
* Name of the Algorithm used.
*
* @return string algorithm name.
*/
public function get_name(): string {
return "PLAINTEXT";
}
public function build_signature($request, $consumer, $token) {
$sig = array(
OAuthUtil::urlencode_rfc3986($consumer->secret)
);
if ($token) {
array_push($sig, OAuthUtil::urlencode_rfc3986($token->secret));
} else {
array_push($sig, '');
}
$raw = implode("&", $sig);
// for debug purposes
$request->base_string = $raw;
return OAuthUtil::urlencode_rfc3986($raw);
}
}
class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
/**
* Name of the Algorithm used.
*
* @return string algorithm name.
*/
public function get_name(): string {
return "RSA-SHA1";
}
protected function fetch_public_cert(&$request) {
// not implemented yet, ideas are:
// (1) do a lookup in a table of trusted certs keyed off of consumer
// (2) fetch via http using a url provided by the requester
// (3) some sort of specific discovery code based on request
//
// either way should return a string representation of the certificate
throw new OAuthException("fetch_public_cert not implemented");
}
protected function fetch_private_cert(&$request) {
// not implemented yet, ideas are:
// (1) do a lookup in a table of trusted certs keyed off of consumer
//
// either way should return a string representation of the certificate
throw new OAuthException("fetch_private_cert not implemented");
}
public function build_signature(&$request, $consumer, $token) {
$base_string = $request->get_signature_base_string();
$request->base_string = $base_string;
// Fetch the private key cert based on the request
$cert = $this->fetch_private_cert($request);
// Pull the private key ID from the certificate
$privatekeyid = openssl_get_privatekey($cert);
// Sign using the key
$ok = openssl_sign($base_string, $signature, $privatekeyid);
// Avoid passing null values to base64_encode.
if (!$ok) {
throw new OAuthException("OpenSSL unable to sign data");
}
// TODO: Remove this block once PHP 8.0 becomes required.
if (PHP_MAJOR_VERSION < 8) {
// Release the key resource
openssl_free_key($privatekeyid);
}
return base64_encode($signature);
}
public function check_signature(&$request, $consumer, $token, $signature) {
$decoded_sig = base64_decode($signature);
$base_string = $request->get_signature_base_string();
// Fetch the public key cert based on the request
$cert = $this->fetch_public_cert($request);
// Pull the public key ID from the certificate
$publickeyid = openssl_get_publickey($cert);
// Check the computed signature against the one passed in the query
$ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
// TODO: Remove this block once PHP 8.0 becomes required.
if (PHP_MAJOR_VERSION < 8) {
// Release the key resource
openssl_free_key($publickeyid);
}
return $ok == 1;
}
}
class OAuthRequest {
private $parameters;
private $http_method;
private $http_url;
// for debug purposes
public $base_string;
public static $version = '1.0';
public static $POST_INPUT = 'php://input';
function __construct($http_method, $http_url, $parameters = null) {
@$parameters or $parameters = array();
$this->parameters = $parameters;
$this->http_method = $http_method;
$this->http_url = $http_url;
}
/**
* attempt to build up a request from what was passed to the server
*/
public static function from_request($http_method = null, $http_url = null, $parameters = null) {
$scheme = (!is_https()) ? 'http' : 'https';
$port = "";
if ($_SERVER['SERVER_PORT'] != "80" && $_SERVER['SERVER_PORT'] != "443" && strpos(':', $_SERVER['HTTP_HOST']) < 0) {
$port = ':' . $_SERVER['SERVER_PORT'];
}
@$http_url or $http_url = $scheme .
'://' . $_SERVER['HTTP_HOST'] .
$port .
$_SERVER['REQUEST_URI'];
@$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
// We weren't handed any parameters, so let's find the ones relevant to
// this request.
// If you run XML-RPC or similar you should use this to provide your own
// parsed parameter-list
if (!$parameters) {
// Find request headers
$request_headers = OAuthUtil::get_headers();
// Parse the query-string to find GET parameters
$parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
$ourpost = $_POST;
// Add POST Parameters if they exist
$parameters = array_merge($parameters, $ourpost);
// We have a Authorization-header with OAuth data. Parse the header
// and add those overriding any duplicates from GET or POST
if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
$header_parameters = OAuthUtil::split_header($request_headers['Authorization']);
$parameters = array_merge($parameters, $header_parameters);
}
}
return new OAuthRequest($http_method, $http_url, $parameters);
}
/**
* pretty much a helper function to set up the request
*/
public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters = null) {
@$parameters or $parameters = array();
$defaults = array(
"oauth_version" => self::$version,
"oauth_nonce" => self::generate_nonce(),
"oauth_timestamp" => self::generate_timestamp(),
"oauth_consumer_key" => $consumer->key
);
if ($token) {
$defaults['oauth_token'] = $token->key;
}
$parameters = array_merge($defaults, $parameters);
// Parse the query-string to find and add GET parameters
$parts = parse_url($http_url);
if (isset($parts['query'])) {
$qparms = OAuthUtil::parse_parameters($parts['query']);
$parameters = array_merge($qparms, $parameters);
}
return new OAuthRequest($http_method, $http_url, $parameters);
}
public function set_parameter($name, $value, $allow_duplicates = true) {
if ($allow_duplicates && isset($this->parameters[$name])) {
// We have already added parameter(s) with this name, so add to the list
if (is_scalar($this->parameters[$name])) {
// This is the first duplicate, so transform scalar (string)
// into an array so we can add the duplicates
$this->parameters[$name] = array($this->parameters[$name]);
}
$this->parameters[$name][] = $value;
} else {
$this->parameters[$name] = $value;
}
}
public function get_parameter($name) {
return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
}
public function get_parameters() {
return $this->parameters;
}
public function unset_parameter($name) {
unset($this->parameters[$name]);
}
/**
* The request parameters, sorted and concatenated into a normalized string.
* @return string
*/
public function get_signable_parameters() {
// Grab all parameters
$params = $this->parameters;
// Remove oauth_signature if present
// Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
if (isset($params['oauth_signature'])) {
unset($params['oauth_signature']);
}
return OAuthUtil::build_http_query($params);
}
/**
* Returns the base string of this request
*
* The base string defined as the method, the url
* and the parameters (normalized), each urlencoded
* and the concated with &.
*/
public function get_signature_base_string() {
$parts = array(
$this->get_normalized_http_method(),
$this->get_normalized_http_url(),
$this->get_signable_parameters()
);
$parts = OAuthUtil::urlencode_rfc3986($parts);
return implode('&', $parts);
}
/**
* just uppercases the http method
*/
public function get_normalized_http_method() {
return strtoupper($this->http_method);
}
/**
* Parses {@see http_url} and returns normalized scheme://host/path if non-empty, otherwise return empty string
*
* @return string
*/
public function get_normalized_http_url() {
if ($this->http_url === '') {
return '';
}
$parts = parse_url($this->http_url);
$port = @$parts['port'];
$scheme = $parts['scheme'];
$host = $parts['host'];
$path = @$parts['path'];
$port or $port = ($scheme == 'https') ? '443' : '80';
if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) {
$host = "$host:$port";
}
return "$scheme://$host$path";
}
/**
* builds a url usable for a GET request
*/
public function to_url() {
$post_data = $this->to_postdata();
$out = $this->get_normalized_http_url();
if ($post_data) {
$out .= '?'.$post_data;
}
return $out;
}
/**
* builds the data one would send in a POST request
*/
public function to_postdata() {
return OAuthUtil::build_http_query($this->parameters);
}
/**
* builds the Authorization: header
*/
public function to_header() {
$out = 'Authorization: OAuth realm=""';
$total = array();
foreach ($this->parameters as $k => $v) {
if (substr($k, 0, 5) != "oauth") {
continue;
}
if (is_array($v)) {
throw new OAuthException('Arrays not supported in headers');
}
$out .= ',' .
OAuthUtil::urlencode_rfc3986($k) .
'="' .
OAuthUtil::urlencode_rfc3986($v) .
'"';
}
return $out;
}
public function __toString() {
return $this->to_url();
}
public function sign_request($signature_method, $consumer, $token) {
$this->set_parameter("oauth_signature_method", $signature_method->get_name(), false);
$signature = $this->build_signature($signature_method, $consumer, $token);
$this->set_parameter("oauth_signature", $signature, false);
}
public function build_signature($signature_method, $consumer, $token) {
$signature = $signature_method->build_signature($this, $consumer, $token);
return $signature;
}
/**
* util function: current timestamp
*/
private static function generate_timestamp() {
return time();
}
/**
* util function: current nonce
*/
private static function generate_nonce() {
$mt = microtime();
$rand = mt_rand();
return md5($mt.$rand); // md5s look nicer than numbers
}
}
class OAuthServer {
protected $timestamp_threshold = 300; // in seconds, five minutes
protected $version = 1.0; // hi blaine
protected $signature_methods = array();
protected $data_store;
function __construct($data_store) {
$this->data_store = $data_store;
}
public function add_signature_method($signature_method) {
$this->signature_methods[$signature_method->get_name()] = $signature_method;
}
// high level functions
/**
* process a request_token request
* returns the request token on success
*/
public function fetch_request_token(&$request) {
$this->get_version($request);
$consumer = $this->get_consumer($request);
// no token required for the initial token request
$token = null;
$this->check_signature($request, $consumer, $token);
$new_token = $this->data_store->new_request_token($consumer);
return $new_token;
}
/**
* process an access_token request
* returns the access token on success
*/
public function fetch_access_token(&$request) {
$this->get_version($request);
$consumer = $this->get_consumer($request);
// requires authorized request token
$token = $this->get_token($request, $consumer, "request");
$this->check_signature($request, $consumer, $token);
$new_token = $this->data_store->new_access_token($token, $consumer);
return $new_token;
}
/**
* verify an api call, checks all the parameters
*/
public function verify_request(&$request) {
global $lastcomputedsignature;
$lastcomputedsignature = false;
$this->get_version($request);
$consumer = $this->get_consumer($request);
$token = $this->get_token($request, $consumer, "access");
$this->check_signature($request, $consumer, $token);
return array(
$consumer,
$token
);
}
// Internals from here
/**
* version 1
*/
private function get_version(&$request) {
$version = $request->get_parameter("oauth_version");
if (!$version) {
$version = 1.0;
}
if ($version && $version != $this->version) {
throw new OAuthException("OAuth version '$version' not supported");
}
return $version;
}
/**
* figure out the signature with some defaults
*/
private function get_signature_method(&$request) {
$signature_method = @ $request->get_parameter("oauth_signature_method");
if (!$signature_method) {
$signature_method = "PLAINTEXT";
}
if (!in_array($signature_method, array_keys($this->signature_methods))) {
throw new OAuthException("Signature method '$signature_method' not supported " .
"try one of the following: " .
implode(", ", array_keys($this->signature_methods)));
}
return $this->signature_methods[$signature_method];
}
/**
* try to find the consumer for the provided request's consumer key
*/
private function get_consumer(&$request) {
$consumer_key = @ $request->get_parameter("oauth_consumer_key");
if (!$consumer_key) {
throw new OAuthException("Invalid consumer key");
}
$consumer = $this->data_store->lookup_consumer($consumer_key);
if (!$consumer) {
throw new OAuthException("Invalid consumer");
}
return $consumer;
}
/**
* try to find the token for the provided request's token key
*/
private function get_token(&$request, $consumer, $token_type = "access") {
$token_field = @ $request->get_parameter('oauth_token');
if (!$token_field) {
return false;
}
$token = $this->data_store->lookup_token($consumer, $token_type, $token_field);
if (!$token) {
throw new OAuthException("Invalid $token_type token: $token_field");
}
return $token;
}
/**
* all-in-one function to check the signature on a request
* should guess the signature method appropriately
*/
private function check_signature(&$request, $consumer, $token) {
// this should probably be in a different method
global $lastcomputedsignature;
$lastcomputedsignature = false;
$timestamp = @ $request->get_parameter('oauth_timestamp');
$nonce = @ $request->get_parameter('oauth_nonce');
$this->check_timestamp($timestamp);
$this->check_nonce($consumer, $token, $nonce, $timestamp);
$signature_method = $this->get_signature_method($request);
$signature = $request->get_parameter('oauth_signature');
$valid_sig = $signature_method->check_signature($request, $consumer, $token, $signature);
if (!$valid_sig) {
$ex_text = "Invalid signature";
if ($lastcomputedsignature) {
$ex_text = $ex_text . " ours= $lastcomputedsignature yours=$signature";
}
throw new OAuthException($ex_text);
}
}
/**
* check that the timestamp is new enough
*/
private function check_timestamp($timestamp) {
// verify that timestamp is recentish
$now = time();
if ($now - $timestamp > $this->timestamp_threshold) {
throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
}
}
/**
* check that the nonce is not repeated
*/
private function check_nonce($consumer, $token, $nonce, $timestamp) {
// verify that the nonce is uniqueish
$found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
if ($found) {
throw new OAuthException("Nonce already used: $nonce");
}
}
}
class OAuthDataStore {
function lookup_consumer($consumer_key) {
// implement me
}
function lookup_token($consumer, $token_type, $token) {
// implement me
}
function lookup_nonce($consumer, $token, $nonce, $timestamp) {
// implement me
}
function new_request_token($consumer) {
// return a new token attached to this consumer
}
function new_access_token($token, $consumer) {
// return a new access token attached to this consumer
// for the user associated with this token if the request token
// is authorized
// should also invalidate the request token
}
}
class OAuthUtil {
public static function urlencode_rfc3986($input) {
if (is_array($input)) {
return array_map(array(
'moodle\mod\lti\OAuthUtil',
'urlencode_rfc3986'
), $input);
} else {
if (is_scalar($input)) {
return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($input)));
} else {
return '';
}
}
}
// This decode function isn't taking into consideration the above
// modifications to the encoding process. However, this method doesn't
// seem to be used anywhere so leaving it as is.
public static function urldecode_rfc3986($string) {
return urldecode($string);
}
// Utility function for turning the Authorization: header into
// parameters, has to do some unescaping
// Can filter out any non-oauth parameters if needed (default behaviour)
public static function split_header($header, $only_allow_oauth_parameters = true) {
$pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
$offset = 0;
$params = array();
while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
$match = $matches[0];
$header_name = $matches[2][0];
$header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
$params[$header_name] = self::urldecode_rfc3986($header_content);
}
$offset = $match[1] + strlen($match[0]);
}
if (isset($params['realm'])) {
unset($params['realm']);
}
return $params;
}
// helper to try to sort out headers for people who aren't running apache
public static function get_headers() {
if (function_exists('apache_request_headers')) {
// we need this to get the actual Authorization: header
// because apache tends to tell us it doesn't exist
$in = apache_request_headers();
$out = array();
foreach ($in as $key => $value) {
$key = str_replace(" ", "-", ucwords(strtolower(str_replace("-", " ", $key))));
$out[$key] = $value;
}
return $out;
}
// otherwise we don't have apache and are just going to have to hope
// that $_SERVER actually contains what we need
$out = array();
foreach ($_SERVER as $key => $value) {
if (substr($key, 0, 5) == "HTTP_") {
// this is chaos, basically it is just there to capitalize the first
// letter of every word that is not an initial HTTP and strip HTTP
// code from przemek
$key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
$out[$key] = $value;
}
}
return $out;
}
// This function takes a input like a=b&a=c&d=e and returns the parsed
// parameters like this
// array('a' => array('b','c'), 'd' => 'e')
public static function parse_parameters($input) {
if (!isset($input) || !$input) {
return array();
}
$pairs = explode('&', $input);
$parsed_parameters = array();
foreach ($pairs as $pair) {
$split = explode('=', $pair, 2);
$parameter = self::urldecode_rfc3986($split[0]);
$value = isset($split[1]) ? self::urldecode_rfc3986($split[1]) : '';
if (isset($parsed_parameters[$parameter])) {
// We have already recieved parameter(s) with this name, so add to the list
// of parameters with this name
if (is_scalar($parsed_parameters[$parameter])) {
// This is the first duplicate, so transform scalar (string) into an array
// so we can add the duplicates
$parsed_parameters[$parameter] = array(
$parsed_parameters[$parameter]
);
}
$parsed_parameters[$parameter][] = $value;
} else {
$parsed_parameters[$parameter] = $value;
}
}
return $parsed_parameters;
}
public static function build_http_query($params) {
if (!$params) {
return '';
}
// Urlencode both keys and values
$keys = self::urlencode_rfc3986(array_keys($params));
$values = self::urlencode_rfc3986(array_values($params));
$params = array_combine($keys, $values);
// Parameters are sorted by name, using lexicographical byte value ordering.
// Ref: Spec: 9.1.1 (1)
uksort($params, 'strcmp');
$pairs = array();
foreach ($params as $parameter => $value) {
if (is_array($value)) {
// If two or more parameters share the same name, they are sorted by their value
// Ref: Spec: 9.1.1 (1)
natsort($value);
foreach ($value as $duplicate_value) {
$pairs[] = $parameter . '=' . $duplicate_value;
}
} else {
$pairs[] = $parameter . '=' . $value;
}
}
// For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
// Each name-value pair is separated by an '&' character (ASCII code 38)
return implode('&', $pairs);
}
}
+132
View File
@@ -0,0 +1,132 @@
<?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.
namespace moodle\mod\lti; // Using a namespace as the basicLTI module imports classes with the same names.
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot . '/mod/lti/OAuth.php');
require_once($CFG->dirroot . '/mod/lti/TrivialStore.php');
/**
*
* @param int $typeid LTI type ID.
* @param string[] $scopes Array of scopes which give permission for the current request.
*
* @return string|int|boolean The OAuth consumer key, the LTI type ID for the validated bearer token,
true for requests not requiring a scope, otherwise false.
*/
function get_oauth_key_from_headers($typeid = null, $scopes = null) {
global $DB;
$now = time();
$requestheaders = OAuthUtil::get_headers();
if (isset($requestheaders['Authorization'])) {
if (substr($requestheaders['Authorization'], 0, 6) == "OAuth ") {
$headerparameters = OAuthUtil::split_header($requestheaders['Authorization']);
return format_string($headerparameters['oauth_consumer_key']);
} else if (empty($scopes)) {
return true;
} else if (substr($requestheaders['Authorization'], 0, 7) == 'Bearer ') {
$tokenvalue = trim(substr($requestheaders['Authorization'], 7));
$conditions = array('token' => $tokenvalue);
if (!empty($typeid)) {
$conditions['typeid'] = intval($typeid);
}
$token = $DB->get_record('lti_access_tokens', $conditions);
if ($token) {
// Log token access.
$DB->set_field('lti_access_tokens', 'lastaccess', $now, array('id' => $token->id));
$permittedscopes = json_decode($token->scope);
if ((intval($token->validuntil) > $now) && !empty(array_intersect($scopes, $permittedscopes))) {
return intval($token->typeid);
}
}
}
}
return false;
}
function handle_oauth_body_post($oauthconsumerkey, $oauthconsumersecret, $body, $requestheaders = null) {
if ($requestheaders == null) {
$requestheaders = OAuthUtil::get_headers();
}
// Must reject application/x-www-form-urlencoded.
if (isset($requestheaders['Content-type'])) {
if ($requestheaders['Content-type'] == 'application/x-www-form-urlencoded' ) {
throw new OAuthException("OAuth request body signing must not use application/x-www-form-urlencoded");
}
}
if (isset($requestheaders['Authorization']) && (substr($requestheaders['Authorization'], 0, 6) == "OAuth ")) {
$headerparameters = OAuthUtil::split_header($requestheaders['Authorization']);
$oauthbodyhash = $headerparameters['oauth_body_hash'];
}
if ( ! isset($oauthbodyhash) ) {
throw new OAuthException("OAuth request body signing requires oauth_body_hash body");
}
// Verify the message signature.
$store = new TrivialOAuthDataStore();
$store->add_consumer($oauthconsumerkey, $oauthconsumersecret);
$server = new OAuthServer($store);
$method = new OAuthSignatureMethod_HMAC_SHA1();
$server->add_signature_method($method);
$request = OAuthRequest::from_request();
try {
$server->verify_request($request);
} catch (\Exception $e) {
$message = $e->getMessage();
throw new OAuthException("OAuth signature failed: " . $message);
}
$postdata = $body;
$hash = base64_encode(sha1($postdata, true));
if ( $hash != $oauthbodyhash ) {
throw new OAuthException("OAuth oauth_body_hash mismatch");
}
return $postdata;
}
+139
View File
@@ -0,0 +1,139 @@
<?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 a Trivial memory-based store - no support for tokens
*
* @package mod_lti
* @copyright IMS Global Learning Consortium
*
* @author Charles Severance csev@umich.edu
*
* @license http://www.apache.org/licenses/LICENSE-2.0
*/
namespace moodle\mod\lti; // Using a namespace as the basicLTI module imports classes with the same names.
defined('MOODLE_INTERNAL') || die;
/**
* A Trivial memory-based store - no support for tokens.
*/
class TrivialOAuthDataStore extends OAuthDataStore {
/** @var array $consumers Array of tool consumer keys and secrets */
private $consumers = array();
/**
* Add a consumer to the array
*
* @param string $consumerkey Consumer key
* @param string $consumersecret Consumer secret
*/
public function add_consumer($consumerkey, $consumersecret) {
$this->consumers[$consumerkey] = $consumersecret;
}
/**
* Get OAuth consumer given its key
*
* @param string $consumerkey Consumer key
*
* @return moodle\mod\lti\OAuthConsumer OAuthConsumer object
*/
public function lookup_consumer($consumerkey) {
if (strpos($consumerkey, "http://" ) === 0) {
$consumer = new OAuthConsumer($consumerkey, "secret", null);
return $consumer;
}
if ( $this->consumers[$consumerkey] ) {
$consumer = new OAuthConsumer($consumerkey, $this->consumers[$consumerkey], null);
return $consumer;
}
return null;
}
/**
* Create a dummy OAuthToken object for a consumer
*
* @param moodle\mod\lti\OAuthConsumer $consumer Consumer
* @param string $tokentype Type of token
* @param string $token Token ID
*
* @return moodle\mod\lti\OAuthToken OAuthToken object
*/
public function lookup_token($consumer, $tokentype, $token) {
return new OAuthToken($consumer, '');
}
/**
* Nonce values are not checked so just return a null
*
* @param moodle\mod\lti\OAuthConsumer $consumer Consumer
* @param string $token Token ID
* @param string $nonce Nonce value
* @param string $timestamp Timestamp
*
* @return null
*/
public function lookup_nonce($consumer, $token, $nonce, $timestamp) {
// Should add some clever logic to keep nonces from
// being reused - for now we are really trusting
// that the timestamp will save us.
return null;
}
/**
* Tokens are not used so just return a null.
*
* @param moodle\mod\lti\OAuthConsumer $consumer Consumer
*
* @return null
*/
public function new_request_token($consumer) {
return null;
}
/**
* Tokens are not used so just return a null.
*
* @param string $token Token ID
* @param moodle\mod\lti\OAuthConsumer $consumer Consumer
*
* @return null
*/
public function new_access_token($token, $consumer) {
return null;
}
}
+14
View File
@@ -0,0 +1,14 @@
/**
* Encapsules the behavior for creating a tool type from a cartridge URL
* in Moodle. Manages the UI while operations are occuring.
*
* See template: mod_lti/cartridge_registration_form
*
* @module mod_lti/cartridge_registration_form
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define("mod_lti/cartridge_registration_form",["jquery","core/ajax","core/notification","mod_lti/tool_type","mod_lti/events","mod_lti/keys","core/str"],(function($,ajax,notification,toolType,ltiEvents,KEYS,str){var SELECTORS_CONSUMER_KEY="#registration-key",SELECTORS_SHARED_SECRET="#registration-secret",SELECTORS_REGISTRATION_FORM="#cartridge-registration-form",SELECTORS_REGISTRATION_SUBMIT_BUTTON="#cartridge-registration-submit",SELECTORS_REGISTRATION_CANCEL_BUTTON="#cartridge-registration-cancel",getSubmitButton=function(){return $(SELECTORS_REGISTRATION_SUBMIT_BUTTON)},submitCartridgeURL=function(){if(getSubmitButton().hasClass("loading"))return!1;var url=$(SELECTORS_REGISTRATION_FORM).attr("data-cartridge-url");if(""===url)return!1;getSubmitButton().addClass("loading");var consumerKey=$(SELECTORS_CONSUMER_KEY).val(),sharedSecret=$(SELECTORS_SHARED_SECRET).val(),promise=toolType.create({cartridgeurl:url,key:consumerKey,secret:sharedSecret});return promise.done((function(){str.get_string("successfullycreatedtooltype","mod_lti").done((function(s){$(document).trigger(ltiEvents.NEW_TOOL_TYPE),$(document).trigger(ltiEvents.STOP_CARTRIDGE_REGISTRATION),$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK,{message:s})})).fail(notification.exception)})).fail((function(){str.get_string("failedtocreatetooltype","mod_lti").done((function(s){$(document).trigger(ltiEvents.NEW_TOOL_TYPE),$(document).trigger(ltiEvents.STOP_CARTRIDGE_REGISTRATION),$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK,{message:s,error:!0})})).fail(notification.exception)})).always((function(){getSubmitButton().removeClass("loading")})),promise},registerEventListeners=function(){$(SELECTORS_REGISTRATION_FORM).submit((function(e){e.preventDefault(),submitCartridgeURL()}));var cancelButton=$(SELECTORS_REGISTRATION_CANCEL_BUTTON);cancelButton.click((function(e){e.preventDefault(),$(document).trigger(ltiEvents.STOP_CARTRIDGE_REGISTRATION)})),cancelButton.keypress((function(e){e.metaKey||e.shiftKey||e.altKey||e.ctrlKey||e.keyCode!=KEYS.ENTER&&e.keyCode!=KEYS.SPACE||(e.preventDefault(),cancelButton.click())}))};return{init:function(){registerEventListeners()}}}));
//# sourceMappingURL=cartridge_registration_form.min.js.map
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
/**
* Launches the modal dialogue that contains the iframe that sends the Content-Item selection request to an
* LTI tool provider that supports Content-Item type message.
*
* See template: mod_lti/contentitem
*
* @module mod_lti/contentitem
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define("mod_lti/contentitem",["jquery","core/notification","core/str","core/templates","mod_lti/form-field","core/modal","core/modal_events"],(function($,notification,str,templates,FormField,Modal,ModalEvents){var dialogue,doneCallback,contentItem={init:function(url,postData,cb){doneCallback=cb;var context={url:url,postData:postData},bodyPromise=templates.render("mod_lti/contentitem",context);if(dialogue)return dialogue.setBody(bodyPromise),void dialogue.show();str.get_string("selectcontent","lti").then((function(title){return Modal.create({title:title,body:bodyPromise,large:!0,show:!0})})).then((function(modal){dialogue=modal,modal.getRoot().on(ModalEvents.hidden,(function(){modal.setBody(""),notification.fetchNotifications()}))})).catch(notification.exception)}},ltiFormFields=[new FormField("name",FormField.TYPES.TEXT,!1,""),new FormField("introeditor",FormField.TYPES.EDITOR,!1,""),new FormField("toolurl",FormField.TYPES.TEXT,!0,""),new FormField("securetoolurl",FormField.TYPES.TEXT,!0,""),new FormField("instructorchoiceacceptgrades",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorchoicesendname",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorchoicesendemailaddr",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorcustomparameters",FormField.TYPES.TEXT,!0,""),new FormField("icon",FormField.TYPES.TEXT,!0,""),new FormField("secureicon",FormField.TYPES.TEXT,!0,""),new FormField("launchcontainer",FormField.TYPES.SELECT,!0,0),new FormField("grade_modgrade_point",FormField.TYPES.TEXT,!1,""),new FormField("lineitemresourceid",FormField.TYPES.TEXT,!0,""),new FormField("lineitemtag",FormField.TYPES.TEXT,!0,""),new FormField("lineitemsubreviewurl",FormField.TYPES.TEXT,!0,""),new FormField("lineitemsubreviewparams",FormField.TYPES.TEXT,!0,"")];const hideElement=e=>{e.setAttribute("hidden","true"),e.setAttribute("aria-hidden","true"),e.setAttribute("tab-index","-1")},showElement=e=>{e.removeAttribute("hidden"),e.setAttribute("aria-hidden","false"),e.setAttribute("tab-index","1")};return window.processContentItemReturnData=function(returnData){var index;if(dialogue&&dialogue.hide(),returnData.multiple){for(index in ltiFormFields)ltiFormFields[index].setFieldValue("name"===ltiFormFields[index].name?"item":null);var variants=[];returnData.multiple.forEach((function(v){variants.push((config=>{const variant={};return["name","toolurl","securetoolurl","instructorcustomparameters","icon","secureicon","launchcontainer","lineitemresourceid","lineitemtag","lineitemsubreviewurl","lineitemsubreviewparams"].forEach((function(name){variant[name]=config[name]||""})),variant["introeditor[text]"]=config.introeditor?config.introeditor.text:"",variant["introeditor[format]"]=config.introeditor?config.introeditor.format:"",1===config.instructorchoiceacceptgrades?(variant.instructorchoiceacceptgrades="1",variant["grade[modgrade_point]"]=config.grade_modgrade_point||"100"):variant.instructorchoiceacceptgrades="0",variant})(v))})),async function(items){const form=document.querySelector("#region-main-box form"),toolArea=form.querySelector('[data-attribute="dynamic-import"]'),buttonGroup=form.querySelector("#fgroup_id_buttonar"),submitAndLaunch=form.querySelector("#id_submitbutton");Array.from(form.children).forEach(hideElement),hideElement(submitAndLaunch);const{html:html,js:js}=await templates.renderForPromise("mod_lti/tool_deeplinking_results",{items:items});await templates.replaceNodeContents(toolArea,html,js),showElement(toolArea),showElement(buttonGroup)}(returnData.multiple);const submitAndCourse=document.querySelector("#id_submitbutton2");submitAndCourse.onclick=e=>{e.preventDefault(),submitAndCourse.disabled=!0;const fd=new FormData(document.querySelector("#region-main-box form")),backToCourse=()=>{document.querySelector("#id_cancel").click()};variants.reduce(((promise,variant)=>{Object.entries(variant).forEach((entry=>fd.set(entry[0],entry[1])));const body=new URLSearchParams(fd),doPost=()=>fetch(document.location.pathname,{method:"post",body:body});return promise.then(doPost).catch(doPost)}),Promise.resolve()).then(backToCourse).catch(backToCourse)}}else{for(index in ltiFormFields){var field=ltiFormFields[index],value=null;void 0!==returnData[field.name]&&(value=returnData[field.name]),field.setFieldValue(value)}field.setFieldValue(value),document.querySelector("#id_selectcontentindicator").innerHTML=returnData.selectcontentindicator}doneCallback&&doneCallback(returnData)},contentItem}));
//# sourceMappingURL=contentitem.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Processes the result of LTI tool creation from a Content-Item message type.
*
* @module mod_lti/contentitem_return
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define("mod_lti/contentitem_return",["jquery"],(function($){return{init:function(returnData){$(window).ready((function(){window!=top?parent.processContentItemReturnData(returnData):window.processContentItemReturnData(returnData)}))}}}));
//# sourceMappingURL=contentitem_return.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"contentitem_return.min.js","sources":["../src/contentitem_return.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Processes the result of LTI tool creation from a Content-Item message type.\n *\n * @module mod_lti/contentitem_return\n * @copyright 2016 Jun Pataleta <jun@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.2\n */\ndefine(['jquery'], function($) {\n return {\n /**\n * Init function.\n *\n * @param {string} returnData The returned data.\n */\n init: function(returnData) {\n // Make sure the window has loaded before we perform processing.\n $(window).ready(function() {\n if (window != top) {\n // Send return data to be processed by the parent window.\n parent.processContentItemReturnData(returnData);\n } else {\n window.processContentItemReturnData(returnData);\n }\n });\n }\n };\n});\n"],"names":["define","$","init","returnData","window","ready","top","parent","processContentItemReturnData"],"mappings":";;;;;;;;AAuBAA,oCAAO,CAAC,WAAW,SAASC,SACjB,CAMHC,KAAM,SAASC,YAEXF,EAAEG,QAAQC,OAAM,WACRD,QAAUE,IAEVC,OAAOC,6BAA6BL,YAEpCC,OAAOI,6BAA6BL"}
+3
View File
@@ -0,0 +1,3 @@
define("mod_lti/course_tools_list",["exports","core/notification","core/pending","core/ajax","core/toast","core/str","core_table/dynamic","core_table/local/dynamic/selectors","./repository"],(function(_exports,_notification,_pending,_ajax,_toast,_str,_dynamic,Selectors,_repository){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_ajax=_interopRequireDefault(_ajax),Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors);_exports.init=()=>{document.addEventListener("click",(event=>{const courseToolDelete=event.target.closest('[data-action="course-tool-delete"]');if(courseToolDelete){event.preventDefault();const deleteBodyStringId=courseToolDelete.dataset.courseToolUsage>0?"deletecoursetoolwithusageconfirm":"deletecoursetoolconfirm",requiredStrings=[{key:"deletecoursetool",component:"mod_lti",param:courseToolDelete.dataset.courseToolName},{key:deleteBodyStringId,component:"mod_lti",param:courseToolDelete.dataset.courseToolName},{key:"delete",component:"core",param:courseToolDelete.dataset.courseToolName},{key:"coursetooldeleted",component:"mod_lti",param:courseToolDelete.dataset.courseToolName}],triggerElement=courseToolDelete.closest(".dropdown").querySelector(".dropdown-toggle");(0,_str.getStrings)(requiredStrings).then((_ref=>{let[modalTitle,modalBody,deleteLabel]=_ref;return _notification.default.deleteCancelPromise(modalTitle,modalBody,deleteLabel,{triggerElement:triggerElement})})).then((()=>{const pendingPromise=new _pending.default("mod_lti/course_tools:delete"),request={methodname:"mod_lti_delete_course_tool_type",args:{tooltypeid:courseToolDelete.dataset.courseToolId}};return _ajax.default.call([request])[0].then((0,_toast.add)((0,_str.getString)("coursetooldeleted","mod_lti",courseToolDelete.dataset.courseToolName))).then((()=>{const tableRoot=triggerElement.closest(Selectors.main.region);return(0,_dynamic.refreshTableContent)(tableRoot)})).then(pendingPromise.resolve).catch(_notification.default.exception)})).catch((()=>{}))}const courseShowInActivityChooser=event.target.closest('[data-action="showinactivitychooser-toggle"]');if(courseShowInActivityChooser){const showInActivityChooserStateToggle="0"===courseShowInActivityChooser.dataset.state?1:0;return(0,_repository.toggleShowInActivityChooser)(courseShowInActivityChooser.dataset.id,courseShowInActivityChooser.dataset.courseid,showInActivityChooserStateToggle)}}))}}));
//# sourceMappingURL=course_tools_list.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Select course categories for LTI tool.
*
* @module mod_lti/coursecategory
* @copyright 2023 Jackson D'souza <jackson.dsouza@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.3
*/
define("mod_lti/coursecategory",[],(function(){function getParents(elem){const parents=[];for(;elem&&elem!==document;elem=elem.parentNode)elem.classList.contains("accordion-group")&&parents.push(elem);return parents}return document.addEventListener("click",(event=>{const checkedbox=event.target.closest(".lticoursecategories");if(checkedbox){const checkboxstatus=checkedbox.checked,categorycontainer=document.querySelector("#collapse"+checkedbox.value);if(categorycontainer){const categorycontainercheckbox=categorycontainer.querySelectorAll('input[type="checkbox"]');for(let i=0;i<categorycontainercheckbox.length;i++)categorycontainercheckbox[i].checked=checkboxstatus}const ltitreecheckbox=document.querySelector(".modltitree").querySelectorAll('input[type="checkbox"]');let listvalue="";for(let i=0;i<ltitreecheckbox.length;i++)ltitreecheckbox[i].checked&&(listvalue=0==listvalue.length?ltitreecheckbox[i].value:listvalue+","+ltitreecheckbox[i].value);document.querySelector('input[name="lti_coursecategories"]').value=listvalue}})),{init:function(selectedcategories){if(selectedcategories.length){const separator=",",values=selectedcategories.split(separator);for(let i=0;i<values.length;i++){const categoryid=document.getElementById("cat-"+values[i]);0!==categoryid.value&&(categoryid.checked=!0);getParents(categoryid).forEach((function(element){const elem=element.querySelector("a.accordion-toggle"),elembody=element.querySelector(".accordion-body");elem&&elem.classList.contains("collapsed")&&elem.classList.remove("collapsed"),elembody&&(elembody.classList.remove("collapse"),elembody.classList.add("show"))}))}}}}}));
//# sourceMappingURL=coursecategory.min.js.map
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
/**
* Provides a list of events that can be triggered in the LTI management
* page.
*
* @module mod_lti/events
* @class events
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define("mod_lti/events",[],(function(){return{NEW_TOOL_TYPE:"lti.tool.type.new",START_EXTERNAL_REGISTRATION:"lti.registration.external.start",STOP_EXTERNAL_REGISTRATION:"lti.registration.external.stop",START_CARTRIDGE_REGISTRATION:"lti.registration.cartridge.start",STOP_CARTRIDGE_REGISTRATION:"lti.registration.cartridge.stop",REGISTRATION_FEEDBACK:"lti.registration.feedback",CAPABILITIES_AGREE:"lti.tool.type.capabilities.agree",CAPABILITIES_DECLINE:"lti.tool.type.capabilities.decline"}}));
//# sourceMappingURL=events.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"events.min.js","sources":["../src/events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Provides a list of events that can be triggered in the LTI management\n * page.\n *\n * @module mod_lti/events\n * @class events\n * @copyright 2015 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine([], function() {\n return /** @alias module:mod_lti/events */ {\n NEW_TOOL_TYPE: 'lti.tool.type.new',\n START_EXTERNAL_REGISTRATION: 'lti.registration.external.start',\n STOP_EXTERNAL_REGISTRATION: 'lti.registration.external.stop',\n START_CARTRIDGE_REGISTRATION: 'lti.registration.cartridge.start',\n STOP_CARTRIDGE_REGISTRATION: 'lti.registration.cartridge.stop',\n REGISTRATION_FEEDBACK: 'lti.registration.feedback',\n CAPABILITIES_AGREE: 'lti.tool.type.capabilities.agree',\n CAPABILITIES_DECLINE: 'lti.tool.type.capabilities.decline',\n };\n});\n"],"names":["define","NEW_TOOL_TYPE","START_EXTERNAL_REGISTRATION","STOP_EXTERNAL_REGISTRATION","START_CARTRIDGE_REGISTRATION","STOP_CARTRIDGE_REGISTRATION","REGISTRATION_FEEDBACK","CAPABILITIES_AGREE","CAPABILITIES_DECLINE"],"mappings":";;;;;;;;;;AAyBAA,wBAAO,IAAI,iBACoC,CACvCC,cAAe,oBACfC,4BAA6B,kCAC7BC,2BAA4B,iCAC5BC,6BAA8B,mCAC9BC,4BAA6B,kCAC7BC,sBAAuB,4BACvBC,mBAAoB,mCACpBC,qBAAsB"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
/**
* Handles the return params from the external registration page after it
* redirects back to Moodle.
*
* See also: mod/lti/externalregistrationreturn.php
*
* @module mod_lti/external_registration_return
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define("mod_lti/external_registration_return",[],(function(){return{init:function(message,error,id,status){window.parent&&window.parent.triggerExternalRegistrationComplete({message:message,error:error,id:id,status:status})}}}));
//# sourceMappingURL=external_registration_return.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"external_registration_return.min.js","sources":["../src/external_registration_return.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Handles the return params from the external registration page after it\n * redirects back to Moodle.\n *\n * See also: mod/lti/externalregistrationreturn.php\n *\n * @module mod_lti/external_registration_return\n * @copyright 2015 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine([], function() {\n return {\n\n /**\n * If this was rendered in an iframe then trigger the external registration\n * complete behaviour in the parent page and provide the params returned from\n * the external registration page.\n *\n * @param {String} message The registration message from the external registration page\n * @param {String} error The registration error message from the external registration page, if\n * there was an error.\n * @param {Integer} id The tool proxy id for the external registration.\n * @param {String} status Whether the external registration was successful or not.\n */\n init: function(message, error, id, status) {\n if (window.parent) {\n window.parent.triggerExternalRegistrationComplete({\n message: message,\n error: error,\n id: id,\n status: status\n });\n }\n }\n };\n});\n"],"names":["define","init","message","error","id","status","window","parent","triggerExternalRegistrationComplete"],"mappings":";;;;;;;;;;;AA0BAA,8CAAO,IAAI,iBACA,CAaHC,KAAM,SAASC,QAASC,MAAOC,GAAIC,QAC3BC,OAAOC,QACPD,OAAOC,OAAOC,oCAAoC,CAC9CN,QAASA,QACTC,MAAOA,MACPC,GAAIA,GACJC,OAAQA"}
+11
View File
@@ -0,0 +1,11 @@
/**
* A module that enables the setting of form field values on the client side.
*
* @module mod_lti/form-field
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define("mod_lti/form-field",["jquery"],(function($){var FormField=function(name,type,resetIfUndefined,defaultValue){this.name=name,this.id="id_"+this.name,this.selector="#"+this.id,this.type=type,this.resetIfUndefined=resetIfUndefined,this.defaultValue=defaultValue};return FormField.TYPES={TEXT:1,SELECT:2,CHECKBOX:3,EDITOR:4},FormField.prototype.setFieldValue=function(value){if(null===value){if(!this.resetIfUndefined)return;value=this.defaultValue}switch(this.type){case FormField.TYPES.CHECKBOX:value?$(this.selector).prop("checked",!0):$(this.selector).prop("checked",!1);break;case FormField.TYPES.EDITOR:if(void 0!==value.text){var attoEditor=$(this.selector+"editable");attoEditor.length?attoEditor.html(value.text):"undefined"!=typeof tinyMCE&&("3"==tinyMCE.majorVersion?tinyMCE.execInstanceCommand(this.id,"mceInsertContent",!1,value.text):tinyMCE.get(this.id).setContent(value.text)),$(this.selector).val(value.text)}break;default:$(this.selector).val(value)}},FormField}));
//# sourceMappingURL=form-field.min.js.map
File diff suppressed because one or more lines are too long
+12
View File
@@ -0,0 +1,12 @@
/**
* A list of keys and their keycodes that are used by the LTI modules.
*
* @module mod_lti/keys
* @class keys
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define("mod_lti/keys",[],(function(){return{ENTER:13,SPACE:32}}));
//# sourceMappingURL=keys.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"keys.min.js","sources":["../src/keys.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A list of keys and their keycodes that are used by the LTI modules.\n *\n * @module mod_lti/keys\n * @class keys\n * @copyright 2015 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine([], function() {\n return /** @alias module:mod_lti/keys */ {\n ENTER: 13,\n SPACE: 32\n };\n});\n"],"names":["define","ENTER","SPACE"],"mappings":";;;;;;;;;AAwBAA,sBAAO,IAAI,iBACkC,CACrCC,MAAO,GACPC,MAAO"}
+3
View File
@@ -0,0 +1,3 @@
define("mod_lti/mod_form",["exports","mod_lti/contentitem"],(function(_exports,_contentitem){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_contentitem=(obj=_contentitem)&&obj.__esModule?obj:{default:obj};var _default={init:courseId=>{const contentItemButton=document.querySelector('[name="selectcontent"]');contentItemButton&&contentItemButton.addEventListener("click",(()=>{const contentItemUrl=contentItemButton.getAttribute("data-contentitemurl"),contentItemId=document.querySelector("#hidden_typeid").value;if(contentItemId){const title=document.querySelector("#id_name").value.trim(),text=document.querySelector("#id_introeditor").value.trim(),postData={id:contentItemId,course:courseId,title:title,text:text};_contentitem.default.init(contentItemUrl,postData,(returnData=>{if(!returnData.multiple){const allowGrades=document.querySelector("#id_instructorchoiceacceptgrades");let allowGradesChangeEvent=new Event("change");if(allowGrades.dispatchEvent(allowGradesChangeEvent),allowGrades.checked){const gradeType=document.querySelector("#id_grade_modgrade_type");gradeType.value="point";let gradeTypeChangeEvent=new Event("change");gradeType.dispatchEvent(gradeTypeChangeEvent)}}}))}}))}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=mod_form.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"mod_form.min.js","sources":["../src/mod_form.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Event handlers for the mod_lti mod_form.\n *\n * @module mod_lti/mod_form\n * @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport ContentItem from 'mod_lti/contentitem';\n\n/**\n * Initialise module.\n *\n * @param {int} courseId the course id.\n */\nconst init = (courseId) => {\n const contentItemButton = document.querySelector('[name=\"selectcontent\"]');\n\n if (!contentItemButton) {\n return;\n }\n\n contentItemButton.addEventListener('click', () => {\n const contentItemUrl = contentItemButton.getAttribute('data-contentitemurl');\n const contentItemId = document.querySelector('#hidden_typeid').value;\n if (contentItemId) {\n const title = document.querySelector('#id_name').value.trim();\n const text = document.querySelector('#id_introeditor').value.trim();\n const postData = {\n id: contentItemId,\n course: courseId,\n title: title,\n text: text\n };\n\n // The callback below is called after the content item has been returned and processed.\n ContentItem.init(contentItemUrl, postData, (returnData) => {\n if (!returnData.multiple) {\n // The state of the grade checkbox has already been set by processContentItemReturnData() but that\n // hasn't fired the click/change event required by formslib to show/hide the dependent grade fields.\n // Fire it now.\n const allowGrades = document.querySelector('#id_instructorchoiceacceptgrades');\n let allowGradesChangeEvent = new Event('change');\n allowGrades.dispatchEvent(allowGradesChangeEvent);\n\n // If the tool is set to accept grades, make sure \"Point\" is selected.\n if (allowGrades.checked) {\n const gradeType = document.querySelector('#id_grade_modgrade_type');\n gradeType.value = \"point\";\n let gradeTypeChangeEvent = new Event('change');\n gradeType.dispatchEvent(gradeTypeChangeEvent);\n }\n }\n });\n }\n });\n};\n\nexport default {\n init: init\n};\n"],"names":["init","courseId","contentItemButton","document","querySelector","addEventListener","contentItemUrl","getAttribute","contentItemId","value","title","trim","text","postData","id","course","returnData","multiple","allowGrades","allowGradesChangeEvent","Event","dispatchEvent","checked","gradeType","gradeTypeChangeEvent"],"mappings":"oQA2Ee,CACXA,KA5CUC,iBACJC,kBAAoBC,SAASC,cAAc,0BAE5CF,mBAILA,kBAAkBG,iBAAiB,SAAS,WAClCC,eAAiBJ,kBAAkBK,aAAa,uBAChDC,cAAgBL,SAASC,cAAc,kBAAkBK,SAC3DD,cAAe,OACTE,MAAQP,SAASC,cAAc,YAAYK,MAAME,OACjDC,KAAOT,SAASC,cAAc,mBAAmBK,MAAME,OACvDE,SAAW,CACbC,GAAIN,cACJO,OAAQd,SACRS,MAAOA,MACPE,KAAMA,2BAIEZ,KAAKM,eAAgBO,UAAWG,iBACnCA,WAAWC,SAAU,OAIhBC,YAAcf,SAASC,cAAc,wCACvCe,uBAAyB,IAAIC,MAAM,aACvCF,YAAYG,cAAcF,wBAGtBD,YAAYI,QAAS,OACfC,UAAYpB,SAASC,cAAc,2BACzCmB,UAAUd,MAAQ,YACde,qBAAuB,IAAIJ,MAAM,UACrCG,UAAUF,cAAcG"}
+10
View File
@@ -0,0 +1,10 @@
define("mod_lti/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* Module to handle AJAX interactions.
*
* @module mod_lti/repository
* @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.toggleShowInActivityChooser=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.toggleShowInActivityChooser=(tooltypeid,courseid,showinactivitychooser)=>_ajax.default.call([{methodname:"mod_lti_toggle_showinactivitychooser",args:{tooltypeid:tooltypeid,courseid:courseid,showinactivitychooser:showinactivitychooser}}])[0]}));
//# sourceMappingURL=repository.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Module to handle AJAX interactions.\n *\n * @module mod_lti/repository\n * @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Toggle coursevisible of a tool\n *\n * @param {Number} tooltypeid Too type id\n * @param {Number} courseid Course ID\n * @param {Number} showinactivitychooser showinactivitychooser state\n * @return {Promise}\n */\nexport const toggleShowInActivityChooser = (\n tooltypeid,\n courseid,\n showinactivitychooser,\n) => Ajax.call([{\n methodname: 'mod_lti_toggle_showinactivitychooser',\n args: {\n tooltypeid,\n courseid,\n showinactivitychooser,\n },\n}])[0];\n"],"names":["tooltypeid","courseid","showinactivitychooser","Ajax","call","methodname","args"],"mappings":";;;;;;;kMAiC2C,CACvCA,WACAC,SACAC,wBACCC,cAAKC,KAAK,CAAC,CACZC,WAAY,uCACZC,KAAM,CACFN,WAAAA,WACAC,SAAAA,SACAC,sBAAAA,0BAEJ"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Provides an interface for a tool proxy in the Moodle server.
*
* @module mod_lti/tool_proxy
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define("mod_lti/tool_proxy",["core/ajax","core/notification"],(function(ajax,notification){return{query:function(args){var request={methodname:"mod_lti_get_tool_proxies",args:args||{}},promise=ajax.call([request])[0];return promise.fail(notification.exception),promise},delete:function(id){var request={methodname:"mod_lti_delete_tool_proxy",args:{id:id}},promise=ajax.call([request])[0];return promise.fail(notification.exception),promise},create:function(args){var request={methodname:"mod_lti_create_tool_proxy",args:args};return ajax.call([request])[0]}}}));
//# sourceMappingURL=tool_proxy.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"tool_proxy.min.js","sources":["../src/tool_proxy.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Provides an interface for a tool proxy in the Moodle server.\n *\n * @module mod_lti/tool_proxy\n * @copyright 2015 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine(['core/ajax', 'core/notification'], function(ajax, notification) {\n return {\n /**\n * Get a list of tool types from Moodle for the given\n * search args.\n *\n * See also:\n * mod/lti/classes/external.php get_tool_types_parameters()\n *\n * @method query\n * @public\n * @param {Object} args Search parameters\n * @return {Promise} jQuery Deferred object\n */\n query: function(args) {\n var request = {\n methodname: 'mod_lti_get_tool_proxies',\n args: args || {}\n };\n\n var promise = ajax.call([request])[0];\n\n promise.fail(notification.exception);\n\n return promise;\n },\n /**\n * Delete a tool proxy from Moodle.\n *\n * @method delete\n * @public\n * @param {Integer} id Tool proxy ID\n * @return {Promise} jQuery Deferred object\n */\n 'delete': function(id) {\n var request = {\n methodname: 'mod_lti_delete_tool_proxy',\n args: {\n id: id\n }\n };\n\n var promise = ajax.call([request])[0];\n\n promise.fail(notification.exception);\n\n return promise;\n },\n\n /**\n * Create a tool proxy in Moodle.\n *\n * The promise will fail if the proxy cannot be created, so you must handle the fail result.\n *\n * See mod/lti/classes/external.php create_tool_proxy_parameters\n *\n * @method create\n * @public\n * @param {Object} args Tool proxy properties\n * @return {Promise} jQuery Deferred object\n */\n create: function(args) {\n var request = {\n methodname: 'mod_lti_create_tool_proxy',\n args: args\n };\n\n var promise = ajax.call([request])[0];\n\n return promise;\n }\n };\n});\n"],"names":["define","ajax","notification","query","args","request","methodname","promise","call","fail","exception","id","create"],"mappings":";;;;;;;;AAuBAA,4BAAO,CAAC,YAAa,sBAAsB,SAASC,KAAMC,oBAC/C,CAaHC,MAAO,SAASC,UACRC,QAAU,CACVC,WAAY,2BACZF,KAAMA,MAAQ,IAGdG,QAAUN,KAAKO,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKP,aAAaQ,WAEnBH,gBAUD,SAASI,QACXN,QAAU,CACVC,WAAY,4BACZF,KAAM,CACFO,GAAIA,KAIRJ,QAAUN,KAAKO,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKP,aAAaQ,WAEnBH,SAeXK,OAAQ,SAASR,UACTC,QAAU,CACVC,WAAY,4BACZF,KAAMA,aAGIH,KAAKO,KAAK,CAACH,UAAU"}
+14
View File
@@ -0,0 +1,14 @@
/**
* Controls all of the behaviour and interaction with a tool type card. These are
* listed on the LTI tool type management page.
*
* See template: mod_lti/tool_proxy_card
*
* @module mod_lti/tool_proxy_card_controller
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define("mod_lti/tool_proxy_card_controller",["jquery","core/ajax","core/notification","core/templates","mod_lti/tool_proxy","mod_lti/events","mod_lti/keys","core/str"],(function($,ajax,notification,templates,toolProxy,ltiEvents,KEYS,str){var SELECTORS_DELETE_BUTTON=".delete",SELECTORS_ACTIVATE_BUTTON=".tool-card-footer a.activate",getTypeId=function(element){return element.attr("data-proxy-id")},clearAllAnnouncements=function(element){element.removeClass("announcement loading success fail capabilities")},stopLoading=function(element){element.removeClass("announcement loading")},deleteType=function(element){var promise=$.Deferred(),typeId=getTypeId(element);return function(element){clearAllAnnouncements(element),element.addClass("announcement loading")}(element),""===typeId?$.Deferred().resolve():(str.get_strings([{key:"delete",component:"mod_lti"},{key:"delete_confirmation",component:"mod_lti"},{key:"delete",component:"mod_lti"},{key:"cancel",component:"core"}]).done((function(strs){notification.confirm(strs[0],strs[1],strs[2],strs[3],(function(){toolProxy.delete(typeId).done((function(){stopLoading(element),function(element){var promise=$.Deferred();return clearAllAnnouncements(element),element.addClass("announcement success"),setTimeout((function(){element.removeClass("announcement success"),promise.resolve()}),2e3),promise}(element).done((function(){element.remove(),promise.resolve()})).fail(notification.exception)})).fail((function(error){!function(element){var promise=$.Deferred();clearAllAnnouncements(element),element.addClass("announcement fail"),setTimeout((function(){element.removeClass("announcement fail"),promise.resolve()}),2e3)}(element),promise.reject(error)}))}),(function(){stopLoading(element),promise.resolve()}))})).fail((function(error){stopLoading(element),notification.exception(error),promise.reject(error)})),promise)},registerEventListeners=function(element){var deleteButton=function(element){return element.find(SELECTORS_DELETE_BUTTON)}(element);deleteButton.click((function(e){e.preventDefault(),deleteType(element)})),deleteButton.keypress((function(e){e.metaKey||e.shiftKey||e.altKey||e.ctrlKey||e.keyCode!=KEYS.ENTER&&e.keyCode!=KEYS.SPACE||(e.preventDefault(),deleteButton.click())}));var activateButton=function(element){return element.find(SELECTORS_ACTIVATE_BUTTON)}(element);activateButton.click((function(e){e.preventDefault(),function(element){var data={proxyid:getTypeId(element)};$(document).trigger(ltiEvents.START_EXTERNAL_REGISTRATION,data)}(element)})),activateButton.keypress((function(e){e.metaKey||e.shiftKey||e.altKey||e.ctrlKey||e.keyCode!=KEYS.ENTER&&e.keyCode!=KEYS.SPACE||(e.preventDefault(),activateButton.click())}))};return{init:function(element){registerEventListeners(element)}}}));
//# sourceMappingURL=tool_proxy_card_controller.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Provides an interface for a tool type in the Moodle server.
*
* @module mod_lti/tool_type
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define("mod_lti/tool_type",["core/ajax","core/notification"],(function(ajax,notification){return{query:function(args){var request={methodname:"mod_lti_get_tool_types",args:args||{}},promise=ajax.call([request])[0];return promise.fail(notification.exception),promise},create:function(args){var request={methodname:"mod_lti_create_tool_type",args:args};return ajax.call([request])[0]},update:function(args){var request={methodname:"mod_lti_update_tool_type",args:args},promise=ajax.call([request])[0];return promise.fail(notification.exception),promise},delete:function(id){var request={methodname:"mod_lti_delete_tool_type",args:{id:id}},promise=ajax.call([request])[0];return promise.fail(notification.exception),promise},getFromToolProxyId:function(id){return this.query({toolproxyid:id})},isCartridge:function(url){var request={methodname:"mod_lti_is_cartridge",args:{url:url}};return ajax.call([request])[0]},constants:{state:{configured:1,pending:2,rejected:3}}}}));
//# sourceMappingURL=tool_type.min.js.map
File diff suppressed because one or more lines are too long
+12
View File
@@ -0,0 +1,12 @@
define("mod_lti/tool_types_and_proxies",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* Provides an interface for external tools in the Moodle server.
*
* @module mod_lti/tool_types_and_proxies
* @class tool_types_and_proxies
* @copyright 2020 Andrew Madden <andrewmadden@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.0
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.query=_exports.count=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.query=args=>{const request={methodname:"mod_lti_get_tool_types_and_proxies",args:args||{}};return _ajax.default.call([request])[0]};_exports.count=args=>{const request={methodname:"mod_lti_get_tool_types_and_proxies_count",args:args||{}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=tool_types_and_proxies.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"tool_types_and_proxies.min.js","sources":["../src/tool_types_and_proxies.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Provides an interface for external tools in the Moodle server.\n *\n * @module mod_lti/tool_types_and_proxies\n * @class tool_types_and_proxies\n * @copyright 2020 Andrew Madden <andrewmadden@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.0\n */\nimport ajax from 'core/ajax';\n\n/**\n * Get a list of LTI tool types and tool proxies from Moodle for the given\n * search args.\n *\n * See also:\n * mod/lti/classes/external.php get_tool_types_and_proxies()\n *\n * @method query\n * @public\n * @param {Object} args Search parameters\n * @return {Promise} Promise that will be resolved when the ajax call returns.\n */\nexport const query = (args) => {\n const request = {\n methodname: 'mod_lti_get_tool_types_and_proxies',\n args: args || {}\n };\n\n return ajax.call([request])[0];\n};\n\n/**\n * Get a count of LTI tool types and tool proxies from Moodle for the given\n * search args.\n *\n * See also:\n * mod/lti/classes/external.php get_tool_types_and_proxies_count()\n *\n * @method count\n * @public\n * @param {Object} args Search parameters\n * @return {Promise} Promise that will be resolved when the ajax call returns.\n */\nexport const count = (args) => {\n const request = {\n methodname: 'mod_lti_get_tool_types_and_proxies_count',\n args: args || {}\n };\n\n return ajax.call([request])[0];\n};\n"],"names":["args","request","methodname","ajax","call"],"mappings":";;;;;;;;;qKAsCsBA,aACZC,QAAU,CACZC,WAAY,qCACZF,KAAMA,MAAQ,WAGXG,cAAKC,KAAK,CAACH,UAAU,mBAeVD,aACZC,QAAU,CACZC,WAAY,2CACZF,KAAMA,MAAQ,WAGXG,cAAKC,KAAK,CAACH,UAAU"}
@@ -0,0 +1,214 @@
// 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/>.
/**
* Encapsules the behavior for creating a tool type from a cartridge URL
* in Moodle. Manages the UI while operations are occuring.
*
* See template: mod_lti/cartridge_registration_form
*
* @module mod_lti/cartridge_registration_form
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/ajax', 'core/notification', 'mod_lti/tool_type', 'mod_lti/events', 'mod_lti/keys', 'core/str'],
function($, ajax, notification, toolType, ltiEvents, KEYS, str) {
var SELECTORS = {
CARTRIDGE_URL: '#cartridge-url',
CONSUMER_KEY: '#registration-key',
SHARED_SECRET: '#registration-secret',
REGISTRATION_FORM: '#cartridge-registration-form',
REGISTRATION_SUBMIT_BUTTON: '#cartridge-registration-submit',
REGISTRATION_CANCEL_BUTTON: '#cartridge-registration-cancel',
};
/**
* Return the URL the user entered for the cartridge.
*
* @method getCartridgeURL
* @private
* @return {String}
*/
var getCartridgeURL = function() {
return $(SELECTORS.REGISTRATION_FORM).attr('data-cartridge-url');
};
/**
* Return the submit button element.
*
* @method getSubmitButton
* @private
* @return {JQuery} jQuery object
*/
var getSubmitButton = function() {
return $(SELECTORS.REGISTRATION_SUBMIT_BUTTON);
};
/**
* Return the cancel button element.
*
* @method getCancelButton
* @private
* @return {JQuery} jQuery object
*/
var getCancelButton = function() {
return $(SELECTORS.REGISTRATION_CANCEL_BUTTON);
};
/**
* Return the value that the user entered for the consumer key.
*
* @method getConsumerKey
* @private
* @return {String} the value entered for consumer key.
*/
var getConsumerKey = function() {
return $(SELECTORS.CONSUMER_KEY).val();
};
/**
* Return the value that the user entered for the shared secret.
*
* @method getSharedSecret
* @private
* @return {String} the value entered for shared secret
*/
var getSharedSecret = function() {
return $(SELECTORS.SHARED_SECRET).val();
};
/**
* Trigger a visual loading indicator.
*
* @method startLoading
* @private
*/
var startLoading = function() {
getSubmitButton().addClass('loading');
};
/**
* Stop the visual loading indicator.
*
* @method stopLoading
* @private
*/
var stopLoading = function() {
getSubmitButton().removeClass('loading');
};
/**
* Check if the page is currently loading.
*
* @method isLoading
* @private
* @return {Boolean}
*/
var isLoading = function() {
return getSubmitButton().hasClass('loading');
};
/**
* Create a tool type from the cartridge URL that the user input. This will
* send an ajax request to the Moodle server to create the Type. The request will
* include the consumer key and secret, if any.
*
* On success the page will be re-rendered to take the user back to the original
* page with the list of tools and an alert notifying them of success.
*
* @method submitCartridgeURL
* @private
* @return {Promise} jQuery Deferred object
*/
var submitCartridgeURL = function() {
if (isLoading()) {
return false;
}
var url = getCartridgeURL();
// No URL? Do nothing.
if (url === "") {
return false;
}
startLoading();
var consumerKey = getConsumerKey();
var sharedSecret = getSharedSecret();
var promise = toolType.create({cartridgeurl: url, key: consumerKey, secret: sharedSecret});
promise.done(function() {
str.get_string('successfullycreatedtooltype', 'mod_lti').done(function(s) {
$(document).trigger(ltiEvents.NEW_TOOL_TYPE);
$(document).trigger(ltiEvents.STOP_CARTRIDGE_REGISTRATION);
$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, {
message: s
});
}).fail(notification.exception);
}).fail(function() {
str.get_string('failedtocreatetooltype', 'mod_lti').done(function(s) {
$(document).trigger(ltiEvents.NEW_TOOL_TYPE);
$(document).trigger(ltiEvents.STOP_CARTRIDGE_REGISTRATION);
$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, {
message: s,
error: true
});
}).fail(notification.exception);
}).always(function() {
stopLoading();
});
return promise;
};
/**
* Sets up the listeners for user interaction on the page.
*
* @method registerEventListeners
* @private
*/
var registerEventListeners = function() {
var form = $(SELECTORS.REGISTRATION_FORM);
form.submit(function(e) {
e.preventDefault();
submitCartridgeURL();
});
var cancelButton = getCancelButton();
cancelButton.click(function(e) {
e.preventDefault();
$(document).trigger(ltiEvents.STOP_CARTRIDGE_REGISTRATION);
});
cancelButton.keypress(function(e) {
if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
e.preventDefault();
cancelButton.click();
}
}
});
};
return /** @alias module:mod_lti/cartridge_registration_form */ {
/**
* Initialise this module.
*/
init: function() {
registerEventListeners();
}
};
});
+243
View File
@@ -0,0 +1,243 @@
// 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/>.
/**
* Launches the modal dialogue that contains the iframe that sends the Content-Item selection request to an
* LTI tool provider that supports Content-Item type message.
*
* See template: mod_lti/contentitem
*
* @module mod_lti/contentitem
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define(
[
'jquery',
'core/notification',
'core/str',
'core/templates',
'mod_lti/form-field',
'core/modal',
'core/modal_events'
],
function($, notification, str, templates, FormField, Modal, ModalEvents) {
var dialogue;
var doneCallback;
var contentItem = {
/**
* Init function.
*
* @param {string} url The URL for the content item selection.
* @param {object} postData The data to be sent for the content item selection request.
* @param {Function} cb The callback to run once the content item has been processed.
*/
init: function(url, postData, cb) {
doneCallback = cb;
var context = {
url: url,
postData: postData
};
var bodyPromise = templates.render('mod_lti/contentitem', context);
if (dialogue) {
// Set dialogue body.
dialogue.setBody(bodyPromise);
// Display the dialogue.
dialogue.show();
return;
}
str.get_string('selectcontent', 'lti').then(function(title) {
return Modal.create({
title: title,
body: bodyPromise,
large: true,
show: true,
});
}).then(function(modal) {
dialogue = modal;
// On hide handler.
modal.getRoot().on(ModalEvents.hidden, function() {
// Empty modal contents when it's hidden.
modal.setBody('');
// Fetch notifications.
notification.fetchNotifications();
});
return;
}).catch(notification.exception);
}
};
/**
* Array of form fields for LTI tool configuration.
*/
var ltiFormFields = [
new FormField('name', FormField.TYPES.TEXT, false, ''),
new FormField('introeditor', FormField.TYPES.EDITOR, false, ''),
new FormField('toolurl', FormField.TYPES.TEXT, true, ''),
new FormField('securetoolurl', FormField.TYPES.TEXT, true, ''),
new FormField('instructorchoiceacceptgrades', FormField.TYPES.CHECKBOX, true, true),
new FormField('instructorchoicesendname', FormField.TYPES.CHECKBOX, true, true),
new FormField('instructorchoicesendemailaddr', FormField.TYPES.CHECKBOX, true, true),
new FormField('instructorcustomparameters', FormField.TYPES.TEXT, true, ''),
new FormField('icon', FormField.TYPES.TEXT, true, ''),
new FormField('secureicon', FormField.TYPES.TEXT, true, ''),
new FormField('launchcontainer', FormField.TYPES.SELECT, true, 0),
new FormField('grade_modgrade_point', FormField.TYPES.TEXT, false, ''),
new FormField('lineitemresourceid', FormField.TYPES.TEXT, true, ''),
new FormField('lineitemtag', FormField.TYPES.TEXT, true, ''),
new FormField('lineitemsubreviewurl', FormField.TYPES.TEXT, true, ''),
new FormField('lineitemsubreviewparams', FormField.TYPES.TEXT, true, '')
];
/**
* Hide the element, including aria and tab index.
* @param {HTMLElement} e the element to be hidden.
*/
const hideElement = (e) => {
e.setAttribute('hidden', 'true');
e.setAttribute('aria-hidden', 'true');
e.setAttribute('tab-index', '-1');
};
/**
* Show the element, including aria and tab index (set to 1).
* @param {HTMLElement} e the element to be shown.
*/
const showElement = (e) => {
e.removeAttribute('hidden');
e.setAttribute('aria-hidden', 'false');
e.setAttribute('tab-index', '1');
};
/**
* When more than one item needs to be added, the UI is simplified
* to just list the items to be added. Form is hidden and the only
* options is (save and return to course) or cancel.
* This function injects the summary to the form page, and hides
* the unneeded elements.
* @param {Object[]} items items to be added to the course.
*/
const showMultipleSummaryAndHideForm = async function(items) {
const form = document.querySelector('#region-main-box form');
const toolArea = form.querySelector('[data-attribute="dynamic-import"]');
const buttonGroup = form.querySelector('#fgroup_id_buttonar');
const submitAndLaunch = form.querySelector('#id_submitbutton');
Array.from(form.children).forEach(hideElement);
hideElement(submitAndLaunch);
const {html, js} = await templates.renderForPromise('mod_lti/tool_deeplinking_results',
{items: items});
await templates.replaceNodeContents(toolArea, html, js);
showElement(toolArea);
showElement(buttonGroup);
};
/**
* Transforms config values aimed at populating the lti mod form to JSON variant
* which are used to insert more than one activity modules in one submit
* by applying variation to the submitted form.
* See /course/modedit.php.
* @private
* @param {Object} config transforms a config to an actual form data to be posted.
* @return {Object} variant that will be used to modify form values on submit.
*/
var configToVariant = (config) => {
const variant = {};
['name', 'toolurl', 'securetoolurl', 'instructorcustomparameters', 'icon', 'secureicon',
'launchcontainer', 'lineitemresourceid', 'lineitemtag', 'lineitemsubreviewurl',
'lineitemsubreviewparams'].forEach(
function(name) {
variant[name] = config[name] || '';
}
);
variant['introeditor[text]'] = config.introeditor ? config.introeditor.text : '';
variant['introeditor[format]'] = config.introeditor ? config.introeditor.format : '';
if (config.instructorchoiceacceptgrades === 1) {
variant.instructorchoiceacceptgrades = '1';
variant['grade[modgrade_point]'] = config.grade_modgrade_point || '100';
} else {
variant.instructorchoiceacceptgrades = '0';
}
return variant;
};
/**
* Window function that can be called from mod_lti/contentitem_return to close the dialogue and process the return data.
* If the return data contains more than one item, the form will not be populated with item data
* but rather hidden, and the item data will be added to a single input field used to create multiple
* instances in one request.
*
* @param {object} returnData The fetched configuration data from the Content-Item selection dialogue.
*/
window.processContentItemReturnData = function(returnData) {
if (dialogue) {
dialogue.hide();
}
var index;
if (returnData.multiple) {
for (index in ltiFormFields) {
// Name is required, so putting a placeholder as it will not be used
// in multi-items add.
ltiFormFields[index].setFieldValue(ltiFormFields[index].name === 'name' ? 'item' : null);
}
var variants = [];
returnData.multiple.forEach(function(v) {
variants.push(configToVariant(v));
});
showMultipleSummaryAndHideForm(returnData.multiple);
const submitAndCourse = document.querySelector('#id_submitbutton2');
submitAndCourse.onclick = (e) => {
e.preventDefault();
submitAndCourse.disabled = true;
const fd = new FormData(document.querySelector('#region-main-box form'));
const postVariant = (promise, variant) => {
Object.entries(variant).forEach((entry) => fd.set(entry[0], entry[1]));
const body = new URLSearchParams(fd);
const doPost = () => fetch(document.location.pathname, {method: 'post', body});
return promise.then(doPost).catch(doPost);
};
const backToCourse = () => {
document.querySelector("#id_cancel").click();
};
variants.reduce(postVariant, Promise.resolve()).then(backToCourse).catch(backToCourse);
};
} else {
// Populate LTI configuration fields from return data.
for (index in ltiFormFields) {
var field = ltiFormFields[index];
var value = null;
if (typeof returnData[field.name] !== 'undefined') {
value = returnData[field.name];
}
field.setFieldValue(value);
}
field.setFieldValue(value);
// Update the UI element which signifies content has been selected.
document.querySelector("#id_selectcontentindicator").innerHTML = returnData.selectcontentindicator;
}
if (doneCallback) {
doneCallback(returnData);
}
};
return contentItem;
}
);
+43
View File
@@ -0,0 +1,43 @@
// 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/>.
/**
* Processes the result of LTI tool creation from a Content-Item message type.
*
* @module mod_lti/contentitem_return
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define(['jquery'], function($) {
return {
/**
* Init function.
*
* @param {string} returnData The returned data.
*/
init: function(returnData) {
// Make sure the window has loaded before we perform processing.
$(window).ready(function() {
if (window != top) {
// Send return data to be processed by the parent window.
parent.processContentItemReturnData(returnData);
} else {
window.processContentItemReturnData(returnData);
}
});
}
};
});
+93
View File
@@ -0,0 +1,93 @@
// 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/>.
/**
* Course LTI External tools list management.
*
* @module mod_lti/course_tools_list
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
"use strict";
import Notification from 'core/notification';
import Pending from 'core/pending';
import Ajax from 'core/ajax';
import {add as addToast} from 'core/toast';
import {getString, getStrings} from 'core/str';
import {refreshTableContent} from 'core_table/dynamic';
import * as Selectors from 'core_table/local/dynamic/selectors';
import {toggleShowInActivityChooser} from "./repository";
/**
* Initialise module.
*/
export const init = () => {
document.addEventListener('click', event => {
const courseToolDelete = event.target.closest('[data-action="course-tool-delete"]');
if (courseToolDelete) {
event.preventDefault();
// A different message is used in the modal if the tool has usages within the course.
const usage = courseToolDelete.dataset.courseToolUsage;
const deleteBodyStringId = usage > 0 ? 'deletecoursetoolwithusageconfirm' : 'deletecoursetoolconfirm';
const requiredStrings = [
{key: 'deletecoursetool', component: 'mod_lti', param: courseToolDelete.dataset.courseToolName},
{key: deleteBodyStringId, component: 'mod_lti', param: courseToolDelete.dataset.courseToolName},
{key: 'delete', component: 'core', param: courseToolDelete.dataset.courseToolName},
{key: 'coursetooldeleted', component: 'mod_lti', param: courseToolDelete.dataset.courseToolName}
];
// Use triggerElement to return focus to the action menu toggle.
const triggerElement = courseToolDelete.closest('.dropdown').querySelector('.dropdown-toggle');
getStrings(requiredStrings).then(([modalTitle, modalBody, deleteLabel]) => {
return Notification.deleteCancelPromise(
modalTitle,
modalBody,
deleteLabel,
{triggerElement});
}).then(() => {
const pendingPromise = new Pending('mod_lti/course_tools:delete');
const request = {
methodname: 'mod_lti_delete_course_tool_type',
args: {tooltypeid: courseToolDelete.dataset.courseToolId}
};
return Ajax.call([request])[0]
.then(addToast(getString('coursetooldeleted', 'mod_lti', courseToolDelete.dataset.courseToolName)))
.then(() => {
const tableRoot = triggerElement.closest(Selectors.main.region);
return refreshTableContent(tableRoot);
})
.then(pendingPromise.resolve)
.catch(Notification.exception);
}).catch(() => {
return;
});
}
const courseShowInActivityChooser = event.target.closest('[data-action="showinactivitychooser-toggle"]');
if (courseShowInActivityChooser) {
const showInActivityChooserStateToggle = courseShowInActivityChooser.dataset.state === "0" ? 1 : 0;
return toggleShowInActivityChooser(
courseShowInActivityChooser.dataset.id,
courseShowInActivityChooser.dataset.courseid,
showInActivityChooserStateToggle,
);
}
});
};
+118
View File
@@ -0,0 +1,118 @@
// 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/>.
/**
* Select course categories for LTI tool.
*
* @module mod_lti/coursecategory
* @copyright 2023 Jackson D'souza <jackson.dsouza@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.3
*/
define([], function() {
document.addEventListener('click', event => {
const checkedbox = event.target.closest(".lticoursecategories");
if (checkedbox) {
// Get checkbox status.
const checkboxstatus = checkedbox.checked;
// Check / Uncheck all child category checkboxes based on selected checkbox status.
const categorycontainer = document.querySelector('#collapse' + checkedbox.value);
if (categorycontainer) {
const categorycontainercheckbox = categorycontainer.querySelectorAll('input[type="checkbox"]');
for (let i = 0; i < categorycontainercheckbox.length; i++) {
categorycontainercheckbox[i].checked = checkboxstatus;
}
}
const lticategorytree = document.querySelector('.modltitree');
const ltitreecheckbox = lticategorytree.querySelectorAll('input[type="checkbox"]');
let listvalue = '';
for (let i = 0; i < ltitreecheckbox.length; i++) {
if (ltitreecheckbox[i].checked) {
if (listvalue.length == 0) {
listvalue = ltitreecheckbox[i].value;
} else {
listvalue = listvalue + ',' + ltitreecheckbox[i].value;
}
}
}
document.querySelector('input[name="lti_coursecategories"]').value = listvalue;
}
});
/**
* Get parent elements with class = accordion.
*
* @method getParents
* @private
* @param {string} elem DOM element.
* @return {array} Parent elements.
*/
function getParents(elem) {
// Set up a parent array
const parents = [];
// Push each parent element to the array
for (; elem && elem !== document; elem = elem.parentNode) {
if (elem.classList.contains('accordion-group')) {
parents.push(elem);
}
}
// Return our parent array
return parents;
}
return /** @alias module:mod_lti/coursecategory */ {
/**
* Initialise this module.
* Loop through checkbox form elements starting with #cat-{N} and set it to checked
* if {N} is found in the Selected category(s) list. Show / Hide the parent UL element.
*
* @param {string} selectedcategories Selected category(s).
*/
init: function(selectedcategories) {
if (selectedcategories.length) {
const separator = ",";
const values = selectedcategories.split(separator);
for (let i = 0; i < values.length; i++) {
const categoryid = document.getElementById("cat-" + values[i]);
if (categoryid.value !== 0) {
categoryid.checked = true;
}
const parents = getParents(categoryid);
parents.forEach(function(element) {
const elem = element.querySelector('a.accordion-toggle');
const elembody = element.querySelector('.accordion-body');
if (elem && elem.classList.contains('collapsed')) {
elem.classList.remove('collapsed');
}
if (elembody) {
elembody.classList.remove('collapse');
elembody.classList.add('show');
}
});
}
}
}
};
});
+37
View File
@@ -0,0 +1,37 @@
// 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/>.
/**
* Provides a list of events that can be triggered in the LTI management
* page.
*
* @module mod_lti/events
* @class events
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define([], function() {
return /** @alias module:mod_lti/events */ {
NEW_TOOL_TYPE: 'lti.tool.type.new',
START_EXTERNAL_REGISTRATION: 'lti.registration.external.start',
STOP_EXTERNAL_REGISTRATION: 'lti.registration.external.stop',
START_CARTRIDGE_REGISTRATION: 'lti.registration.cartridge.start',
STOP_CARTRIDGE_REGISTRATION: 'lti.registration.cartridge.stop',
REGISTRATION_FEEDBACK: 'lti.registration.feedback',
CAPABILITIES_AGREE: 'lti.tool.type.capabilities.agree',
CAPABILITIES_DECLINE: 'lti.tool.type.capabilities.decline',
};
});
+639
View File
@@ -0,0 +1,639 @@
// 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/>.
/**
* Encapsules the behavior for creating a tool type and tool proxy from a
* registration url in Moodle.
*
* Manages the UI while operations are occuring, including rendering external
* registration page within the iframe.
*
* See template: mod_lti/external_registration
*
* @module mod_lti/external_registration
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/events',
'mod_lti/tool_proxy', 'mod_lti/tool_type', 'mod_lti/keys', 'core/str'],
function($, ajax, notification, templates, ltiEvents, toolProxy, toolType, KEYS, str) {
var SELECTORS = {
EXTERNAL_REGISTRATION_CONTAINER: '#external-registration-page-container',
EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER: '#external-registration-template-container',
EXTERNAL_REGISTRATION_CANCEL_BUTTON: '#cancel-external-registration',
TOOL_TYPE_CAPABILITIES_CONTAINER: '#tool-type-capabilities-container',
TOOL_TYPE_CAPABILITIES_TEMPLATE_CONTAINER: '#tool-type-capabilities-template-container',
CAPABILITIES_AGREE_CONTAINER: '.capabilities-container',
};
/**
* Return the external registration cancel button element. This button is
* the cancel button that appears while the iframe is rendered.
*
* @method getExternalRegistrationCancelButton
* @private
* @return {JQuery} jQuery object
*/
var getExternalRegistrationCancelButton = function() {
return $(SELECTORS.EXTERNAL_REGISTRATION_CANCEL_BUTTON);
};
/**
* Return the container that holds all elements for the external registration, including
* the cancel button and the iframe.
*
* @method getExternalRegistrationContainer
* @private
* @return {JQuery} jQuery object
*/
var getExternalRegistrationContainer = function() {
return $(SELECTORS.EXTERNAL_REGISTRATION_CONTAINER);
};
/**
* Return the container that holds the external registration page template. It should
* be the iframe.
*
* @method getExternalRegistrationTemplateContainer
* @private
* @return {JQuery} jQuery object
*/
var getExternalRegistrationTemplateContainer = function() {
return $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);
};
/**
* Return the container that holds the elements for displaying the list of capabilities
* that this tool type requires. This container wraps the loading indicator and the template
* container.
*
* @method getToolTypeCapabilitiesContainer
* @private
* @return {JQuery} jQuery object
*/
var getToolTypeCapabilitiesContainer = function() {
return $(SELECTORS.TOOL_TYPE_CAPABILITIES_CONTAINER);
};
/**
* Return the container that holds the template that lists the capabilities that the
* tool type will require.
*
* @method getToolTypeCapabilitiesTemplateContainer
* @private
* @return {JQuery} jQuery object
*/
var getToolTypeCapabilitiesTemplateContainer = function() {
return $(SELECTORS.TOOL_TYPE_CAPABILITIES_TEMPLATE_CONTAINER);
};
/**
* Triggers a visual indicator to show that the capabilities section is loading.
*
* @method startLoadingCapabilitiesContainer
* @private
*/
var startLoadingCapabilitiesContainer = function() {
getToolTypeCapabilitiesContainer().addClass('loading');
};
/**
* Removes the visual indicator that shows the capabilities section is loading.
*
* @method stopLoadingCapabilitiesContainer
* @private
*/
var stopLoadingCapabilitiesContainer = function() {
getToolTypeCapabilitiesContainer().removeClass('loading');
};
/**
* Adds a visual indicator that shows the cancel button is loading.
*
* @method startLoadingCancel
* @private
*/
var startLoadingCancel = function() {
getExternalRegistrationCancelButton().addClass('loading');
};
/**
* Adds a visual indicator that shows the cancel button is loading.
*
* @method startLoadingCancel
* @private
*/
var stopLoadingCancel = function() {
getExternalRegistrationCancelButton().removeClass('loading');
};
/**
* Stops displaying the tool type capabilities container.
*
* @method hideToolTypeCapabilitiesContainer
* @private
*/
var hideToolTypeCapabilitiesContainer = function() {
getToolTypeCapabilitiesContainer().addClass('hidden');
};
/**
* Displays the tool type capabilities container.
*
* @method showToolTypeCapabilitiesContainer
* @private
*/
var showToolTypeCapabilitiesContainer = function() {
getToolTypeCapabilitiesContainer().removeClass('hidden');
};
/**
* Stops displaying the external registration content.
*
* @method hideExternalRegistrationContent
* @private
*/
var hideExternalRegistrationContent = function() {
getExternalRegistrationContainer().addClass('hidden');
};
/**
* Displays the external registration content.
*
* @method showExternalRegistrationContent
* @private
*/
var showExternalRegistrationContent = function() {
getExternalRegistrationContainer().removeClass('hidden');
};
/**
* Save the given tool proxy id on the DOM.
*
* @method setToolProxyId
* @private
* @param {Integer} id Tool proxy ID
*/
var setToolProxyId = function(id) {
var button = getExternalRegistrationCancelButton();
button.attr('data-tool-proxy-id', id);
};
/**
* Return the saved tool proxy id.
*
* @method getToolProxyId
* @private
* @return {String} Tool proxy ID
*/
var getToolProxyId = function() {
var button = getExternalRegistrationCancelButton();
return button.attr('data-tool-proxy-id');
};
/**
* Remove the saved tool proxy id.
*
* @method clearToolProxyId
* @private
*/
var clearToolProxyId = function() {
var button = getExternalRegistrationCancelButton();
button.removeAttr('data-tool-proxy-id');
};
/**
* Returns true if a tool proxy id has been recorded.
*
* @method hasToolProxyId
* @private
* @return {Boolean}
*/
var hasToolProxyId = function() {
return getToolProxyId() ? true : false;
};
/**
* Checks if this process has created a tool proxy within
* Moodle yet.
*
* @method hasCreatedToolProxy
* @private
* @return {Boolean}
*/
var hasCreatedToolProxy = function() {
var button = getExternalRegistrationCancelButton();
return button.attr('data-tool-proxy-new') && hasToolProxyId();
};
/**
* Records that this process has created a tool proxy.
*
* @method setProxyAsNew
* @private
* @return {Boolean}
*/
var setProxyAsNew = function() {
var button = getExternalRegistrationCancelButton();
return button.attr('data-tool-proxy-new', "new");
};
/**
* Records that this process has not created a tool proxy.
*
* @method setProxyAsOld
* @private
* @return {Boolean}
*/
var setProxyAsOld = function() {
var button = getExternalRegistrationCancelButton();
return button.removeAttr('data-tool-proxy-new');
};
/**
* Gets the external registration request required to be sent to the external
* registration page using a form.
*
* See mod_lti/tool_proxy_registration_form template.
*
* @method getRegistrationRequest
* @private
* @param {Integer} id Tool Proxy ID
* @return {Promise} jQuery Deferred object
*/
var getRegistrationRequest = function(id) {
var request = {
methodname: 'mod_lti_get_tool_proxy_registration_request',
args: {
id: id
}
};
return ajax.call([request])[0];
};
/**
* Cancel an in progress external registration. This will perform any necessary
* clean up of tool proxies and return the page section back to the home section.
*
* @method cancelRegistration
* @private
* @return {Promise} jQuery Deferred object
*/
var cancelRegistration = function() {
startLoadingCancel();
var promise = $.Deferred();
// If we've created a proxy as part of this process then
// we need to delete it to clean up the data in the back end.
if (hasCreatedToolProxy()) {
var id = getToolProxyId();
toolProxy.delete(id).done(function() {
promise.resolve();
}).fail(function(failure) {
promise.reject(failure);
});
} else {
promise.resolve();
}
promise.done(function() {
// Return to the original page.
finishExternalRegistration();
stopLoadingCancel();
}).fail(function(failure) {
notification.exception(failure);
finishExternalRegistration();
stopLoadingCancel();
str.get_string('failedtodeletetoolproxy', 'mod_lti').done(function(s) {
var feedback = {
message: s,
error: true
};
$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, feedback);
}).fail(notification.exception);
});
return promise;
};
/**
* Load the external registration template and render it in the DOM and display it.
*
* @method renderExternalRegistrationWindow
* @private
* @param {Object} registrationRequest
* @return {Promise} jQuery Deferred object
*/
var renderExternalRegistrationWindow = function(registrationRequest) {
var promise = templates.render('mod_lti/tool_proxy_registration_form', registrationRequest);
promise.done(function(html, js) {
// Show the external registration page in an iframe.
var container = getExternalRegistrationTemplateContainer();
container.append(html);
templates.runTemplateJS(js);
container.find('form').submit();
showExternalRegistrationContent();
}).fail(notification.exception);
return promise;
};
/**
* Send a request to Moodle server to set the state of the tool type to configured (active).
*
* @method setTypeStatusActive
* @private
* @param {Object} typeData A set of data representing a type, as returned by a request to get a type
* from the Moodle server.
* @return {Promise} jQuery Deferred object
*/
var setTypeStatusActive = function(typeData) {
return toolType.update({
id: typeData.id,
state: toolType.constants.state.configured
});
};
/**
* Render and display an agreement page for the user to acknowledge the list of capabilities
* (groups of data) that the external tool requires in order to work. If the user agrees then
* we will activate the tool so that it is immediately available. If they don't agree then
* the tool remains in a pending state within Moodle until agreement is given.
*
* @method promptForToolTypeCapabilitiesAgreement
* @private
* @param {Object} typeData A set of data representing a type, as returned by a request to get a type
* from the Moodle server.
* @return {Promise} jQuery Deferred object
*/
var promptForToolTypeCapabilitiesAgreement = function(typeData) {
var promise = $.Deferred();
templates.render('mod_lti/tool_type_capabilities_agree', typeData).done(function(html, js) {
var container = getToolTypeCapabilitiesTemplateContainer();
hideExternalRegistrationContent();
showToolTypeCapabilitiesContainer();
templates.replaceNodeContents(container, html, js);
var choiceContainer = container.find(SELECTORS.CAPABILITIES_AGREE_CONTAINER);
// The user agrees to allow the tool to use the groups of data so we can go
// ahead and activate it for them so that it can be used straight away.
choiceContainer.on(ltiEvents.CAPABILITIES_AGREE, function() {
startLoadingCapabilitiesContainer();
setTypeStatusActive(typeData).always(function() {
stopLoadingCapabilitiesContainer();
container.empty();
promise.resolve();
});
});
// The user declines to let the tool use the data. In this case we leave
// the tool as pending and they can delete it using the main screen if they
// wish.
choiceContainer.on(ltiEvents.CAPABILITIES_DECLINE, function() {
container.empty();
promise.resolve();
});
}).fail(promise.reject);
promise.done(function() {
hideToolTypeCapabilitiesContainer();
}).fail(notification.exception);
return promise;
};
/**
* Send a request to the Moodle server to create a tool proxy using the registration URL the user
* has provided. The proxy is required for the external registration page to work correctly.
*
* After the proxy is created the external registration page is rendered within an iframe for the user
* to complete the registration in the external page.
*
* If the tool proxy creation fails then we redirect the page section back to the home section and
* display the error, rather than rendering the external registration page.
*
* @method createAndRegisterToolProxy
* @private
* @param {String} url Tool registration URL to register
* @return {Promise} jQuery Deferred object
*/
var createAndRegisterToolProxy = function(url) {
var promise = $.Deferred();
if (!url || url === "") {
// No URL has been input so do nothing.
promise.resolve();
} else {
// A tool proxy needs to exist before the external page is rendered because
// the external page sends requests back to Moodle for information that is stored
// in the proxy.
toolProxy.create({regurl: url})
.done(function(result) {
// Note that it's a new proxy so we will always clean it up.
setProxyAsNew();
promise = registerProxy(result.id);
})
.fail(function(exception) {
// Clean up.
cancelRegistration();
// Let the user know what the error is.
var feedback = {
message: exception.message,
error: true
};
$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, feedback);
promise.reject(exception);
});
}
return promise;
};
/**
* Loads the window to register a proxy, given an ID.
*
* @method registerProxy
* @private
* @param {Integer} id Proxy id to register
* @return {Promise} jQuery Deferred object to fail or resolve
*/
var registerProxy = function(id) {
var promise = $.Deferred();
// Save the id on the DOM to cleanup later.
setToolProxyId(id);
// There is a specific set of data needed to send to the external registration page
// in a form, so let's get it from our server.
getRegistrationRequest(id)
.done(function(registrationRequest) {
renderExternalRegistrationWindow(registrationRequest)
.done(function() {
promise.resolve();
})
.fail(promise.fail);
})
.fail(promise.fail);
return promise;
};
/**
* Complete the registration process, clean up any left over data and
* trigger the appropriate events.
*
* @method finishExternalRegistration
* @private
*/
var finishExternalRegistration = function() {
if (hasToolProxyId()) {
clearToolProxyId();
}
setProxyAsOld(false);
hideExternalRegistrationContent();
var container = getExternalRegistrationTemplateContainer();
container.empty();
$(document).trigger(ltiEvents.STOP_EXTERNAL_REGISTRATION);
};
/**
* Sets up the listeners for user interaction on the page.
*
* @method registerEventListeners
* @private
*/
var registerEventListeners = function() {
$(document).on(ltiEvents.START_EXTERNAL_REGISTRATION, function(event, data) {
if (!data) {
return;
}
if (data.url) {
createAndRegisterToolProxy(data.url);
}
if (data.proxyid) {
registerProxy(data.proxyid);
}
});
var cancelExternalRegistrationButton = getExternalRegistrationCancelButton();
cancelExternalRegistrationButton.click(function(e) {
e.preventDefault();
cancelRegistration();
});
cancelExternalRegistrationButton.keypress(function(e) {
if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
e.preventDefault();
cancelRegistration();
}
}
});
// This is gross but necessary due to isolated jQuery scopes between
// child iframe and parent windows. There is no other way to communicate.
//
// This function gets called by the moodle page that received the redirect
// from the external registration page and handles the external page's returned
// parameters.
//
// See AMD module mod_lti/external_registration_return.
window.triggerExternalRegistrationComplete = function(data) {
var promise = $.Deferred();
var feedback = {
message: "",
error: false
};
if (data.status == "success") {
str.get_string('successfullycreatedtooltype', 'mod_lti').done(function(s) {
feedback.message = s;
}).fail(notification.exception);
// Trigger appropriate events when we've completed the necessary requests.
promise.done(function() {
finishExternalRegistration();
$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, feedback);
$(document).trigger(ltiEvents.NEW_TOOL_TYPE);
}).fail(notification.exception);
// We should have created a tool proxy by this point.
if (hasCreatedToolProxy()) {
var proxyId = getToolProxyId();
// We need the list of types that are linked to this proxy. We're assuming it'll
// only be one because this process creates a one-to-one type->proxy.
toolType.getFromToolProxyId(proxyId).done(function(types) {
if (types && types.length) {
// There should only be one result.
var typeData = types[0];
// Check if the external tool required access to any Moodle data (users, courses etc).
if (typeData.hascapabilitygroups) {
// If it did then we ask the user to agree to those groups before the type is
// activated (i.e. can be used in Moodle).
promptForToolTypeCapabilitiesAgreement(typeData).always(function() {
promise.resolve();
});
} else {
promise.resolve();
}
} else {
promise.resolve();
}
}).fail(function() {
promise.resolve();
});
}
} else {
// Anything other than success is failure.
feedback.message = data.error;
feedback.error = true;
// Cancel registration to clean up any proxies and tools that were
// created.
promise.done(function() {
cancelRegistration().always(function() {
$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, feedback);
});
}).fail(notification.exception);
promise.resolve();
}
return promise;
};
};
return {
/**
* Initialise this module.
*/
init: function() {
registerEventListeners();
}
};
});
@@ -0,0 +1,52 @@
// 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/>.
/**
* Handles the return params from the external registration page after it
* redirects back to Moodle.
*
* See also: mod/lti/externalregistrationreturn.php
*
* @module mod_lti/external_registration_return
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define([], function() {
return {
/**
* If this was rendered in an iframe then trigger the external registration
* complete behaviour in the parent page and provide the params returned from
* the external registration page.
*
* @param {String} message The registration message from the external registration page
* @param {String} error The registration error message from the external registration page, if
* there was an error.
* @param {Integer} id The tool proxy id for the external registration.
* @param {String} status Whether the external registration was successful or not.
*/
init: function(message, error, id, status) {
if (window.parent) {
window.parent.triggerExternalRegistrationComplete({
message: message,
error: error,
id: id,
status: status
});
}
}
};
});
+111
View File
@@ -0,0 +1,111 @@
// 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/>.
/**
* A module that enables the setting of form field values on the client side.
*
* @module mod_lti/form-field
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define(['jquery'],
function($) {
/**
* Form field class.
*
* @param {string} name Field name.
* @param {number} type The field type.
* @param {boolean} resetIfUndefined Flag to reset the field to the default value if undefined in the return data.
* @param {string|number|boolean} defaultValue The default value to use for the field.
* @constructor
*/
var FormField = function(name, type, resetIfUndefined, defaultValue) {
this.name = name;
this.id = 'id_' + this.name;
this.selector = '#' + this.id;
this.type = type;
this.resetIfUndefined = resetIfUndefined;
this.defaultValue = defaultValue;
};
/**
* Form field types.
*
* @type {{TEXT: number, SELECT: number, CHECKBOX: number, EDITOR: number}}
*/
FormField.TYPES = {
TEXT: 1,
SELECT: 2,
CHECKBOX: 3,
EDITOR: 4
};
/**
* Sets the values for a form field.
*
* @param {string|boolean|number} value The value to be set into the field.
*/
FormField.prototype.setFieldValue = function(value) {
if (value === null) {
if (this.resetIfUndefined) {
value = this.defaultValue;
} else {
// No need set the field value if value is null and there's no need to reset the field.
return;
}
}
switch (this.type) {
case FormField.TYPES.CHECKBOX:
if (value) {
$(this.selector).prop('checked', true);
} else {
$(this.selector).prop('checked', false);
}
break;
case FormField.TYPES.EDITOR:
if (typeof value.text !== 'undefined') {
/* global tinyMCE:false */
// Set text in editor's editable content, if applicable.
// Check if it is an Atto editor.
var attoEditor = $(this.selector + 'editable');
if (attoEditor.length) {
attoEditor.html(value.text);
} else if (typeof tinyMCE !== 'undefined') {
// If the editor is not Atto, try to fallback to TinyMCE.
if (tinyMCE.majorVersion == "3") {
// Tiny 3.
tinyMCE.execInstanceCommand(this.id, 'mceInsertContent', false, value.text);
} else {
// Tiny 4+.
tinyMCE.get(this.id).setContent(value.text);
}
}
// Set text to actual editor text area.
$(this.selector).val(value.text);
}
break;
default:
$(this.selector).val(value);
break;
}
};
return FormField;
}
);
+30
View File
@@ -0,0 +1,30 @@
// 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/>.
/**
* A list of keys and their keycodes that are used by the LTI modules.
*
* @module mod_lti/keys
* @class keys
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define([], function() {
return /** @alias module:mod_lti/keys */ {
ENTER: 13,
SPACE: 32
};
});
+78
View File
@@ -0,0 +1,78 @@
// 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/>.
/**
* Event handlers for the mod_lti mod_form.
*
* @module mod_lti/mod_form
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
"use strict";
import ContentItem from 'mod_lti/contentitem';
/**
* Initialise module.
*
* @param {int} courseId the course id.
*/
const init = (courseId) => {
const contentItemButton = document.querySelector('[name="selectcontent"]');
if (!contentItemButton) {
return;
}
contentItemButton.addEventListener('click', () => {
const contentItemUrl = contentItemButton.getAttribute('data-contentitemurl');
const contentItemId = document.querySelector('#hidden_typeid').value;
if (contentItemId) {
const title = document.querySelector('#id_name').value.trim();
const text = document.querySelector('#id_introeditor').value.trim();
const postData = {
id: contentItemId,
course: courseId,
title: title,
text: text
};
// The callback below is called after the content item has been returned and processed.
ContentItem.init(contentItemUrl, postData, (returnData) => {
if (!returnData.multiple) {
// The state of the grade checkbox has already been set by processContentItemReturnData() but that
// hasn't fired the click/change event required by formslib to show/hide the dependent grade fields.
// Fire it now.
const allowGrades = document.querySelector('#id_instructorchoiceacceptgrades');
let allowGradesChangeEvent = new Event('change');
allowGrades.dispatchEvent(allowGradesChangeEvent);
// If the tool is set to accept grades, make sure "Point" is selected.
if (allowGrades.checked) {
const gradeType = document.querySelector('#id_grade_modgrade_type');
gradeType.value = "point";
let gradeTypeChangeEvent = new Event('change');
gradeType.dispatchEvent(gradeTypeChangeEvent);
}
}
});
}
});
};
export default {
init: init
};
+45
View File
@@ -0,0 +1,45 @@
// 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/>.
/**
* Module to handle AJAX interactions.
*
* @module mod_lti/repository
* @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* Toggle coursevisible of a tool
*
* @param {Number} tooltypeid Too type id
* @param {Number} courseid Course ID
* @param {Number} showinactivitychooser showinactivitychooser state
* @return {Promise}
*/
export const toggleShowInActivityChooser = (
tooltypeid,
courseid,
showinactivitychooser,
) => Ajax.call([{
methodname: 'mod_lti_toggle_showinactivitychooser',
args: {
tooltypeid,
courseid,
showinactivitychooser,
},
}])[0];
+696
View File
@@ -0,0 +1,696 @@
// 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/>.
/**
* Controls all of the behaviour and interaction with a tool type card. These are
* listed on the LTI tool type management page.
*
* See template: mod_lti/tool_card
*
* @module mod_lti/tool_card_controller
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'core/modal',
'mod_lti/tool_type', 'mod_lti/events', 'mod_lti/keys',
'core/str'],
function($, ajax, notification, templates, Modal, toolType, ltiEvents, KEYS, str) {
var SELECTORS = {
DELETE_BUTTON: '.delete',
NAME_ELEMENT: '.name',
DESCRIPTION_ELEMENT: '.description',
CAPABILITIES_CONTAINER: '.capabilities-container',
ACTIVATE_BUTTON: '.tool-card-footer a.activate',
};
// Timeout in seconds.
var ANNOUNCEMENT_TIMEOUT = 2000;
/**
* Return the delete button element.
*
* @method getDeleteButton
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {JQuery} jQuery object
*/
var getDeleteButton = function(element) {
return element.find(SELECTORS.DELETE_BUTTON);
};
/**
* Return the element representing the tool type name.
*
* @method getNameElement
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {JQuery} jQuery object
*/
var getNameElement = function(element) {
return element.find(SELECTORS.NAME_ELEMENT);
};
/**
* Return the element representing the tool type description.
*
* @method getDescriptionElement
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {JQuery} jQuery object
*/
var getDescriptionElement = function(element) {
return element.find(SELECTORS.DESCRIPTION_ELEMENT);
};
/**
* Return the activate button for the type.
*
* @method getActivateButton
* @private
* @param {Object} element jQuery object representing the tool card.
* @return {Object} jQuery object
*/
var getActivateButton = function(element) {
return element.find(SELECTORS.ACTIVATE_BUTTON);
};
/**
* Checks if the type card has an activate button.
*
* @method hasActivateButton
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Boolean} true if has active buton
*/
var hasActivateButton = function(element) {
return getActivateButton(element).length ? true : false;
};
/**
* Return the element that contains the capabilities approval for
* the user.
*
* @method getCapabilitiesContainer
* @private
* @param {Object} element jQuery object representing the tool card.
* @return {Object} The element
*/
var getCapabilitiesContainer = function(element) {
return element.find(SELECTORS.CAPABILITIES_CONTAINER);
};
/**
* Checks if the tool type has capabilities that need approval. If it
* does then the container will be present.
*
* @method hasCapabilitiesContainer
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Boolean} true if has capbilities.
*/
var hasCapabilitiesContainer = function(element) {
return getCapabilitiesContainer(element).length ? true : false;
};
/**
* Get the type id.
*
* @method getTypeId
* @private
* @param {Object} element jQuery object representing the tool card.
* @return {String} Type ID
*/
var getTypeId = function(element) {
return element.attr('data-type-id');
};
/**
* Stop any announcement currently visible on the card.
*
* @method clearAllAnnouncements
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var clearAllAnnouncements = function(element) {
element.removeClass('announcement loading success fail capabilities');
};
/**
* Show the loading announcement.
*
* @method startLoading
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var startLoading = function(element) {
clearAllAnnouncements(element);
element.addClass('announcement loading');
};
/**
* Hide the loading announcement.
*
* @method stopLoading
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var stopLoading = function(element) {
element.removeClass('announcement loading');
};
/**
* Show the success announcement. The announcement is only
* visible for 2 seconds.
*
* @method announceSuccess
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var announceSuccess = function(element) {
var promise = $.Deferred();
clearAllAnnouncements(element);
element.addClass('announcement success');
setTimeout(function() {
element.removeClass('announcement success');
promise.resolve();
}, ANNOUNCEMENT_TIMEOUT);
return promise;
};
/**
* Show the failure announcement. The announcement is only
* visible for 2 seconds.
*
* @method announceFailure
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var announceFailure = function(element) {
var promise = $.Deferred();
clearAllAnnouncements(element);
element.addClass('announcement fail');
setTimeout(function() {
element.removeClass('announcement fail');
promise.resolve();
}, ANNOUNCEMENT_TIMEOUT);
return promise;
};
/**
* Delete the tool type from the Moodle server. Triggers a success
* or failure announcement depending on the result.
*
* @method deleteType
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var deleteType = function(element) {
var promise = $.Deferred();
var typeId = getTypeId(element);
startLoading(element);
if (typeId === "") {
return $.Deferred().resolve();
}
str.get_strings([
{
key: 'delete',
component: 'mod_lti'
},
{
key: 'delete_confirmation',
component: 'mod_lti'
},
{
key: 'delete',
component: 'mod_lti'
},
{
key: 'cancel',
component: 'core'
},
])
.done(function(strs) {
notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
toolType.delete(typeId)
.done(function() {
stopLoading(element);
announceSuccess(element)
.done(function() {
element.remove();
})
.fail(notification.exception)
.always(function() {
// Always resolve because even if the announcement fails the type was deleted.
promise.resolve();
});
})
.fail(function(error) {
announceFailure(element);
promise.reject(error);
});
}, function() {
stopLoading(element);
promise.resolve();
});
})
.fail(function(error) {
stopLoading(element);
notification.exception(error);
promise.reject(error);
});
return promise;
};
/**
* Save a given value in a data attribute on the element.
*
* @method setValueSnapshot
* @private
* @param {JQuery} element jQuery object representing the element.
* @param {String} value to be saved.
*/
var setValueSnapshot = function(element, value) {
element.attr('data-val-snapshot', value);
};
/**
* Return the saved value from the element.
*
* @method getValueSnapshot
* @private
* @param {JQuery} element jQuery object representing the element.
* @return {String} the saved value.
*/
var getValueSnapshot = function(element) {
return element.attr('data-val-snapshot');
};
/**
* Save the current value of the tool description.
*
* @method snapshotDescription
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var snapshotDescription = function(element) {
var descriptionElement = getDescriptionElement(element);
if (descriptionElement.hasClass('loading')) {
return;
}
var description = descriptionElement.text().trim();
setValueSnapshot(descriptionElement, description);
};
/**
* Send a request to update the description value for this tool
* in the Moodle server.
*
* @method updateDescription
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var updateDescription = function(element) {
var typeId = getTypeId(element);
// Return early if we don't have an id because it's
// required to save the changes.
if (typeId === "") {
return $.Deferred().resolve();
}
var descriptionElement = getDescriptionElement(element);
// Return early if we're already saving a value.
if (descriptionElement.hasClass('loading')) {
return $.Deferred().resolve();
}
var description = descriptionElement.text().trim();
var snapshotVal = getValueSnapshot(descriptionElement);
// If the value hasn't change then don't bother sending the
// update request.
if (snapshotVal == description) {
return $.Deferred().resolve();
}
descriptionElement.addClass('loading');
var promise = toolType.update({id: typeId, description: description});
promise.done(function(type) {
descriptionElement.removeClass('loading');
// Make sure the text is updated with the description from the
// server, just in case the update didn't work.
descriptionElement.text(type.description);
}).fail(notification.exception);
// Probably need to handle failures better so that we can revert
// the value in the input for the user.
promise.fail(function() {
descriptionElement.removeClass('loading');
});
return promise;
};
/**
* Save the current value of the tool name.
*
* @method snapshotName
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var snapshotName = function(element) {
var nameElement = getNameElement(element);
if (nameElement.hasClass('loading')) {
return;
}
var name = nameElement.text().trim();
setValueSnapshot(nameElement, name);
};
/**
* Send a request to update the name value for this tool
* in the Moodle server.
*
* @method updateName
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var updateName = function(element) {
var typeId = getTypeId(element);
// Return if we don't have an id.
if (typeId === "") {
return $.Deferred().resolve();
}
var nameElement = getNameElement(element);
// Return if we're already saving.
if (nameElement.hasClass('loading')) {
return $.Deferred().resolve();
}
var name = nameElement.text().trim();
var snapshotVal = getValueSnapshot(nameElement);
// If the value hasn't change then don't bother sending the
// update request.
if (snapshotVal == name) {
return $.Deferred().resolve();
}
nameElement.addClass('loading');
var promise = toolType.update({id: typeId, name: name});
promise.done(function(type) {
nameElement.removeClass('loading');
// Make sure the text is updated with the name from the
// server, just in case the update didn't work.
nameElement.text(type.name);
});
// Probably need to handle failures better so that we can revert
// the value in the input for the user.
promise.fail(function() {
nameElement.removeClass('loading');
});
return promise;
};
/**
* Send a request to update the state for this tool to be configured (active)
* in the Moodle server. A success or failure announcement is triggered depending
* on the result.
*
* @method setStatusActive
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var setStatusActive = function(element) {
var id = getTypeId(element);
// Return if we don't have an id.
if (id === "") {
return $.Deferred().resolve();
}
startLoading(element);
var promise = toolType.update({
id: id,
state: toolType.constants.state.configured
});
promise.then(function(toolTypeData) {
stopLoading(element);
announceSuccess(element);
return toolTypeData;
}).then(function(toolTypeData) {
return templates.render('mod_lti/tool_card', toolTypeData);
}).then(function(html, js) {
templates.replaceNode(element, html, js);
return;
}).catch(function() {
stopLoading(element);
announceFailure(element);
});
return promise;
};
/**
* Show the capabilities approval screen to show which groups of data this
* type requires access to in Moodle (if any).
*
* @method displayCapabilitiesApproval
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var displayCapabilitiesApproval = function(element) {
element.addClass('announcement capabilities');
};
/**
* Hide the capabilities approval screen.
*
* @method hideCapabilitiesApproval
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var hideCapabilitiesApproval = function(element) {
element.removeClass('announcement capabilities');
};
/**
* The user wishes to activate this tool so show them the capabilities that
* they need to agree to or if there are none then set the tool type's state
* to active.
*
* @method activateToolType
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var activateToolType = function(element) {
if (hasCapabilitiesContainer(element)) {
displayCapabilitiesApproval(element);
} else {
setStatusActive(element);
}
};
/**
* Sets up the listeners for user interaction on this tool type card.
*
* @method registerEventListeners
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var registerEventListeners = function(element) {
var deleteButton = getDeleteButton(element);
deleteButton.click(function(e) {
e.preventDefault();
deleteType(element);
});
deleteButton.keypress(function(e) {
if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
e.preventDefault();
deleteButton.click();
}
}
});
var descriptionElement = getDescriptionElement(element);
descriptionElement.focus(function(e) {
e.preventDefault();
// Save a copy of the current value for the description so that
// we can check if the user has changed it before sending a request to
// the server.
snapshotDescription(element);
});
descriptionElement.blur(function(e) {
e.preventDefault();
updateDescription(element);
});
descriptionElement.keypress(function(e) {
if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.keyCode == KEYS.ENTER) {
e.preventDefault();
descriptionElement.blur();
}
}
});
var nameElement = getNameElement(element);
nameElement.focus(function(e) {
e.preventDefault();
// Save a copy of the current value for the name so that
// we can check if the user has changed it before sending a request to
// the server.
snapshotName(element);
});
nameElement.blur(function(e) {
e.preventDefault();
updateName(element);
});
nameElement.keypress(function(e) {
if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.keyCode == KEYS.ENTER) {
e.preventDefault();
nameElement.blur();
}
}
});
// Only pending tool type cards have an activate button.
if (hasActivateButton(element)) {
var activateButton = getActivateButton(element);
activateButton.click(function(e) {
e.preventDefault();
activateToolType(element);
});
activateButton.keypress(function(e) {
if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
e.preventDefault();
activateButton.click();
}
}
});
}
if (hasCapabilitiesContainer(element)) {
var capabilitiesContainer = getCapabilitiesContainer(element);
capabilitiesContainer.on(ltiEvents.CAPABILITIES_AGREE, function() {
setStatusActive(element);
});
capabilitiesContainer.on(ltiEvents.CAPABILITIES_DECLINE, function() {
hideCapabilitiesApproval(element);
});
}
};
/**
* Sets up the templates for the tool configuration modal on this tool type card.
*
* @method registerModal
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var registerModal = function(element) {
const configurationLink = element.find('#' + element.data('uniqid') + '-' + element.data('deploymentid'));
if (!configurationLink.length) {
return;
}
const trigger = configurationLink.get(0);
trigger.addEventListener('click', (e) => {
e.preventDefault();
var context = {
'uniqid': element.data('uniqid'),
'platformid': element.data('platformid'),
'clientid': element.data('clientid'),
'deploymentid': element.data('deploymentid'),
'urls': {
'publickeyset': element.data('publickeyseturl'),
'accesstoken': element.data('accesstokenurl'),
'authrequest': element.data('authrequesturl')
}
};
var bodyPromise = templates.render('mod_lti/tool_config_modal_body', context);
var mailTo = 'mailto:?subject=' + encodeURIComponent(element.data('mailtosubject')) +
'&body=' + encodeURIComponent(element.data('platformidstr')) + ':%20' +
encodeURIComponent(element.data('platformid')) + '%0D%0A' +
encodeURIComponent(element.data('clientidstr')) + ':%20' +
encodeURIComponent(element.data('clientid')) + '%0D%0A' +
encodeURIComponent(element.data('deploymentidstr')) + ':%20' +
encodeURIComponent(element.data('deploymentid')) + '%0D%0A' +
encodeURIComponent(element.data('publickeyseturlstr')) + ':%20' +
encodeURIComponent(element.data('publickeyseturl')) + '%0D%0A' +
encodeURIComponent(element.data('accesstokenurlstr')) + ':%20' +
encodeURIComponent(element.data('accesstokenurl')) + '%0D%0A' +
encodeURIComponent(element.data('authrequesturlstr')) + ':%20' +
encodeURIComponent(element.data('authrequesturl')) + '%0D%0A';
context = {
'mailto': mailTo
};
var footerPromise = templates.render('mod_lti/tool_config_modal_footer', context);
Modal.create({
large: true,
title: element.data('modaltitle'),
body: bodyPromise,
footer: footerPromise,
show: true
});
});
};
return /** @alias module:mod_lti/tool_card_controller */ {
/**
* Initialise this module.
*
* @param {JQuery} element jQuery object representing the tool card.
*/
init: function(element) {
registerEventListeners(element);
registerModal(element);
}
};
});
@@ -0,0 +1,528 @@
// 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/>.
/**
* Standard Ajax wrapper for Moodle. It calls the central Ajax script,
* which can call any existing webservice using the current session.
* In addition, it can batch multiple requests and return multiple responses.
*
* @module mod_lti/tool_configure_controller
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/ajax', 'core/paged_content_factory', 'core/notification', 'core/templates', 'mod_lti/events',
'mod_lti/keys', 'mod_lti/tool_types_and_proxies', 'mod_lti/tool_type', 'mod_lti/tool_proxy', 'core/str', 'core/config'],
function($, ajax,
pagedContentFactory, notification, templates, ltiEvents, KEYS,
toolTypesAndProxies, toolType, toolProxy, str, config) {
var SELECTORS = {
EXTERNAL_REGISTRATION_CONTAINER: '#external-registration-container',
EXTERNAL_REGISTRATION_PAGE_CONTAINER: '#external-registration-page-container',
EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER: '#external-registration-template-container',
CARTRIDGE_REGISTRATION_CONTAINER: '#cartridge-registration-container',
CARTRIDGE_REGISTRATION_FORM: '#cartridge-registration-form',
ADD_TOOL_FORM: '#add-tool-form',
TOOL_CARD_CONTAINER: '#tool-card-container',
TOOL_LIST_CONTAINER: '#tool-list-container',
TOOL_CREATE_BUTTON: '#tool-create-button',
TOOL_CREATE_LTILEGACY_BUTTON: '#tool-createltilegacy-button',
REGISTRATION_CHOICE_CONTAINER: '#registration-choice-container',
TOOL_URL: '#tool-url'
};
/**
* Get the tool list container element.
*
* @method getToolListContainer
* @private
* @return {Object} jQuery object
*/
var getToolListContainer = function() {
return $(SELECTORS.TOOL_LIST_CONTAINER);
};
/**
* Get the tool card container element.
*
* @method getToolCardContainer
* @private
* @return {Object} jQuery object
*/
const getToolCardContainer = function() {
return $(SELECTORS.TOOL_CARD_CONTAINER);
};
/**
* Get the external registration container element.
*
* @method getExternalRegistrationContainer
* @private
* @return {Object} jQuery object
*/
var getExternalRegistrationContainer = function() {
return $(SELECTORS.EXTERNAL_REGISTRATION_CONTAINER);
};
/**
* Get the cartridge registration container element.
*
* @method getCartridgeRegistrationContainer
* @private
* @return {Object} jQuery object
*/
var getCartridgeRegistrationContainer = function() {
return $(SELECTORS.CARTRIDGE_REGISTRATION_CONTAINER);
};
/**
* Get the registration choice container element.
*
* @method getRegistrationChoiceContainer
* @private
* @return {Object} jQuery object
*/
var getRegistrationChoiceContainer = function() {
return $(SELECTORS.REGISTRATION_CHOICE_CONTAINER);
};
/**
* Close the LTI Advantage Registration IFrame.
*
* @private
* @param {Object} e post message event sent from the registration frame.
*/
var closeLTIAdvRegistration = function(e) {
if (e.data && 'org.imsglobal.lti.close' === e.data.subject) {
$(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER).empty();
hideExternalRegistration();
showRegistrationChoices();
showToolList();
showRegistrationChoices();
reloadToolList();
}
};
/**
* Load the external registration template and render it in the DOM and display it.
*
* @method initiateRegistration
* @private
* @param {String} url where to send the registration request
*/
var initiateRegistration = function(url) {
// Show the external registration page in an iframe.
$(SELECTORS.EXTERNAL_REGISTRATION_PAGE_CONTAINER).removeClass('hidden');
var container = $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);
container.append($("<iframe src='startltiadvregistration.php?url="
+ encodeURIComponent(url) + "&sesskey=" + config.sesskey + "'></iframe>"));
showExternalRegistration();
window.addEventListener("message", closeLTIAdvRegistration, false);
};
/**
* Get the tool type URL.
*
* @method getToolURL
* @private
* @return {String} the tool type url
*/
var getToolURL = function() {
return $(SELECTORS.TOOL_URL).val();
};
/**
* Hide the external registration container.
*
* @method hideExternalRegistration
* @private
*/
var hideExternalRegistration = function() {
getExternalRegistrationContainer().addClass('hidden');
};
/**
* Hide the cartridge registration container.
*
* @method hideCartridgeRegistration
* @private
*/
var hideCartridgeRegistration = function() {
getCartridgeRegistrationContainer().addClass('hidden');
};
/**
* Hide the registration choice container.
*
* @method hideRegistrationChoices
* @private
*/
var hideRegistrationChoices = function() {
getRegistrationChoiceContainer().addClass('hidden');
};
/**
* Display the external registration panel and hides the other
* panels.
*
* @method showExternalRegistration
* @private
*/
var showExternalRegistration = function() {
hideCartridgeRegistration();
hideRegistrationChoices();
getExternalRegistrationContainer().removeClass('hidden');
screenReaderAnnounce(getExternalRegistrationContainer());
};
/**
* Display the cartridge registration panel and hides the other
* panels.
*
* @method showCartridgeRegistration
* @param {String} url
* @private
*/
var showCartridgeRegistration = function(url) {
hideExternalRegistration();
hideRegistrationChoices();
// Don't save the key and secret from the last tool.
var container = getCartridgeRegistrationContainer();
container.find('input').val('');
container.removeClass('hidden');
container.find(SELECTORS.CARTRIDGE_REGISTRATION_FORM).attr('data-cartridge-url', url);
screenReaderAnnounce(container);
};
/**
* Display the registration choices panel and hides the other
* panels.
*
* @method showRegistrationChoices
* @private
*/
var showRegistrationChoices = function() {
hideExternalRegistration();
hideCartridgeRegistration();
getRegistrationChoiceContainer().removeClass('hidden');
screenReaderAnnounce(getRegistrationChoiceContainer());
};
/**
* JAWS does not notice visibility changes with aria-live.
* Remove and add the content back to force it to read it out.
* This function can be removed once JAWS supports visibility.
*
* @method screenReaderAnnounce
* @param {Object} element
* @private
*/
var screenReaderAnnounce = function(element) {
var children = element.children().detach();
children.appendTo(element);
};
/**
* Hides the list of tool types.
*
* @method hideToolList
* @private
*/
var hideToolList = function() {
getToolListContainer().addClass('hidden');
};
/**
* Display the list of tool types.
*
* @method hideToolList
* @private
*/
var showToolList = function() {
getToolListContainer().removeClass('hidden');
};
/**
* Display the registration feedback alert and hide the other panels.
*
* @method showRegistrationFeedback
* @param {Object} data
* @private
*/
var showRegistrationFeedback = function(data) {
var type = data.error ? 'error' : 'success';
notification.addNotification({
message: data.message,
type: type
});
};
/**
* Show the loading animation
*
* @method startLoading
* @private
* @param {Object} element jQuery object
*/
var startLoading = function(element) {
element.addClass("loading");
};
/**
* Hide the loading animation
*
* @method stopLoading
* @private
* @param {Object} element jQuery object
*/
var stopLoading = function(element) {
element.removeClass("loading");
};
/**
* Refresh the list of tool types and render the new ones.
*
* @method reloadToolList
* @private
*/
var reloadToolList = function() {
// Behat tests should wait for the tool list to load.
M.util.js_pending('reloadToolList');
const cardContainer = getToolCardContainer();
const listContainer = getToolListContainer();
const limit = 60;
// Get initial data with zero limit and offset.
fetchToolCount().done(function(data) {
pagedContentFactory.createWithTotalAndLimit(
data.count,
limit,
function(pagesData) {
return pagesData.map(function(pageData) {
return fetchToolData(pageData.limit, pageData.offset)
.then(function(data) {
return renderToolData(data);
});
});
},
{
'showFirstLast': true
})
.done(function(html, js) {
// Add the paged content into the page.
templates.replaceNodeContents(cardContainer, html, js);
})
.always(function() {
stopLoading(listContainer);
M.util.js_complete('reloadToolList');
});
});
startLoading(listContainer);
};
/**
* Fetch the count of tool type and proxy datasets.
*
* @return {*|void}
*/
const fetchToolCount = function() {
return toolTypesAndProxies.count({'orphanedonly': true})
.done(function(data) {
return data;
}).catch(function(error) {
// Add debug message, then return empty data.
notification.exception(error);
return {
'count': 0
};
});
};
/**
* Fetch the data for tool type and proxy cards.
*
* @param {number} limit Maximum number of datasets to get.
* @param {number} offset Offset count for fetching the data.
* @return {*|void}
*/
const fetchToolData = function(limit, offset) {
const args = {'orphanedonly': true};
// Only add limit and offset to args if they are integers and not null, otherwise defaults will be used.
if (limit !== null && !Number.isNaN(limit)) {
args.limit = limit;
}
if (offset !== null && !Number.isNaN(offset)) {
args.offset = offset;
}
return toolTypesAndProxies.query(args)
.done(function(data) {
return data;
}).catch(function(error) {
// Add debug message, then return empty data.
notification.exception(error);
return {
'types': [],
'proxies': [],
'limit': limit,
'offset': offset
};
});
};
/**
* Render Tool and Proxy cards from data.
*
* @param {Object} data Contains arrays of data objects to populate cards.
* @return {*}
*/
const renderToolData = function(data) {
const context = {
tools: data.types,
proxies: data.proxies,
};
return templates.render('mod_lti/tool_list', context)
.done(function(html, js) {
return {html, js};
}
);
};
/**
* Start the LTI Advantage registration.
*
* @method addLTIAdvTool
* @private
*/
var addLTIAdvTool = function() {
var url = getToolURL().trim();
if (url) {
$(SELECTORS.TOOL_URL).val('');
hideToolList();
initiateRegistration(url);
}
};
/**
* Trigger appropriate registration process process for the user input
* URL. It can either be a cartridge or a registration url.
*
* @method addLTILegacyTool
* @private
* @return {Promise} jQuery Deferred object
*/
var addLTILegacyTool = function() {
var url = getToolURL().trim();
if (url === "") {
return $.Deferred().resolve();
}
var toolButton = $(SELECTORS.TOOL_CREATE_LTILEGACY_BUTTON);
startLoading(toolButton);
var promise = toolType.isCartridge(url);
promise.always(function() {
stopLoading(toolButton);
});
promise.done(function(result) {
if (result.iscartridge) {
$(SELECTORS.TOOL_URL).val('');
$(document).trigger(ltiEvents.START_CARTRIDGE_REGISTRATION, url);
} else {
$(document).trigger(ltiEvents.START_EXTERNAL_REGISTRATION, {url: url});
}
});
promise.fail(function() {
str.get_string('errorbadurl', 'mod_lti')
.done(function(s) {
$(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, {
message: s,
error: true
});
})
.fail(notification.exception);
});
return promise;
};
/**
* Sets up the listeners for user interaction on the page.
*
* @method registerEventListeners
* @private
*/
var registerEventListeners = function() {
// These are events fired by the registration processes. Either
// the cartridge registration or the external registration url.
$(document).on(ltiEvents.NEW_TOOL_TYPE, function() {
reloadToolList();
});
$(document).on(ltiEvents.START_EXTERNAL_REGISTRATION, function() {
showExternalRegistration();
$(SELECTORS.TOOL_URL).val('');
hideToolList();
});
$(document).on(ltiEvents.STOP_EXTERNAL_REGISTRATION, function() {
showToolList();
showRegistrationChoices();
});
$(document).on(ltiEvents.START_CARTRIDGE_REGISTRATION, function(event, url) {
showCartridgeRegistration(url);
});
$(document).on(ltiEvents.STOP_CARTRIDGE_REGISTRATION, function() {
getCartridgeRegistrationContainer().find(SELECTORS.CARTRIDGE_REGISTRATION_FORM).removeAttr('data-cartridge-url');
showRegistrationChoices();
});
$(document).on(ltiEvents.REGISTRATION_FEEDBACK, function(event, data) {
showRegistrationFeedback(data);
});
var addLegacyButton = $(SELECTORS.TOOL_CREATE_LTILEGACY_BUTTON);
addLegacyButton.click(function(e) {
e.preventDefault();
addLTILegacyTool();
});
var addLTIButton = $(SELECTORS.TOOL_CREATE_BUTTON);
addLTIButton.click(function(e) {
e.preventDefault();
addLTIAdvTool();
});
};
return /** @alias module:mod_lti/cartridge_registration_form */ {
/**
* Initialise this module.
*/
init: function() {
registerEventListeners();
reloadToolList();
}
};
});
+96
View File
@@ -0,0 +1,96 @@
// 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/>.
/**
* Provides an interface for a tool proxy in the Moodle server.
*
* @module mod_lti/tool_proxy
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['core/ajax', 'core/notification'], function(ajax, notification) {
return {
/**
* Get a list of tool types from Moodle for the given
* search args.
*
* See also:
* mod/lti/classes/external.php get_tool_types_parameters()
*
* @method query
* @public
* @param {Object} args Search parameters
* @return {Promise} jQuery Deferred object
*/
query: function(args) {
var request = {
methodname: 'mod_lti_get_tool_proxies',
args: args || {}
};
var promise = ajax.call([request])[0];
promise.fail(notification.exception);
return promise;
},
/**
* Delete a tool proxy from Moodle.
*
* @method delete
* @public
* @param {Integer} id Tool proxy ID
* @return {Promise} jQuery Deferred object
*/
'delete': function(id) {
var request = {
methodname: 'mod_lti_delete_tool_proxy',
args: {
id: id
}
};
var promise = ajax.call([request])[0];
promise.fail(notification.exception);
return promise;
},
/**
* Create a tool proxy in Moodle.
*
* The promise will fail if the proxy cannot be created, so you must handle the fail result.
*
* See mod/lti/classes/external.php create_tool_proxy_parameters
*
* @method create
* @public
* @param {Object} args Tool proxy properties
* @return {Promise} jQuery Deferred object
*/
create: function(args) {
var request = {
methodname: 'mod_lti_create_tool_proxy',
args: args
};
var promise = ajax.call([request])[0];
return promise;
}
};
});
@@ -0,0 +1,282 @@
// 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/>.
/**
* Controls all of the behaviour and interaction with a tool type card. These are
* listed on the LTI tool type management page.
*
* See template: mod_lti/tool_proxy_card
*
* @module mod_lti/tool_proxy_card_controller
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/tool_proxy', 'mod_lti/events', 'mod_lti/keys',
'core/str'],
function($, ajax, notification, templates, toolProxy, ltiEvents, KEYS, str) {
var SELECTORS = {
DELETE_BUTTON: '.delete',
CAPABILITIES_CONTAINER: '.capabilities-container',
ACTIVATE_BUTTON: '.tool-card-footer a.activate',
};
// Timeout in seconds.
var ANNOUNCEMENT_TIMEOUT = 2000;
/**
* Return the delete button element.
*
* @method getDeleteButton
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {JQuery} jQuery object
*/
var getDeleteButton = function(element) {
return element.find(SELECTORS.DELETE_BUTTON);
};
/**
* Return the activate button for the type.
*
* @method getActivateButton
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {JQuery} jQuery object
*/
var getActivateButton = function(element) {
return element.find(SELECTORS.ACTIVATE_BUTTON);
};
/**
* Get the type id.
*
* @method getTypeId
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {String} Type ID
*/
var getTypeId = function(element) {
return element.attr('data-proxy-id');
};
/**
* Stop any announcement currently visible on the card.
*
* @method clearAllAnnouncements
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var clearAllAnnouncements = function(element) {
element.removeClass('announcement loading success fail capabilities');
};
/**
* Show the loading announcement.
*
* @method startLoading
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var startLoading = function(element) {
clearAllAnnouncements(element);
element.addClass('announcement loading');
};
/**
* Hide the loading announcement.
*
* @method stopLoading
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var stopLoading = function(element) {
element.removeClass('announcement loading');
};
/**
* Show the success announcement. The announcement is only
* visible for 2 seconds.
*
* @method announceSuccess
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var announceSuccess = function(element) {
var promise = $.Deferred();
clearAllAnnouncements(element);
element.addClass('announcement success');
setTimeout(function() {
element.removeClass('announcement success');
promise.resolve();
}, ANNOUNCEMENT_TIMEOUT);
return promise;
};
/**
* Show the failure announcement. The announcement is only
* visible for 2 seconds.
*
* @method announceFailure
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var announceFailure = function(element) {
var promise = $.Deferred();
clearAllAnnouncements(element);
element.addClass('announcement fail');
setTimeout(function() {
element.removeClass('announcement fail');
promise.resolve();
}, ANNOUNCEMENT_TIMEOUT);
return promise;
};
/**
* Delete the tool type from the Moodle server. Triggers a success
* or failure announcement depending on the result.
*
* @method deleteType
* @private
* @param {JQuery} element jQuery object representing the tool card.
* @return {Promise} jQuery Deferred object
*/
var deleteType = function(element) {
var promise = $.Deferred();
var typeId = getTypeId(element);
startLoading(element);
if (typeId === "") {
return $.Deferred().resolve();
}
str.get_strings([
{
key: 'delete',
component: 'mod_lti'
},
{
key: 'delete_confirmation',
component: 'mod_lti'
},
{
key: 'delete',
component: 'mod_lti'
},
{
key: 'cancel',
component: 'core'
},
])
.done(function(strs) {
notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
toolProxy.delete(typeId)
.done(function() {
stopLoading(element);
announceSuccess(element)
.done(function() {
element.remove();
promise.resolve();
})
.fail(notification.exception);
})
.fail(function(error) {
announceFailure(element);
promise.reject(error);
});
}, function() {
stopLoading(element);
promise.resolve();
});
})
.fail(function(error) {
stopLoading(element);
notification.exception(error);
promise.reject(error);
});
return promise;
};
/**
* The user wishes to activate this tool so show them the capabilities that
* they need to agree to or if there are none then set the tool type's state
* to active.
*
* @method activateToolType
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var activateToolType = function(element) {
var data = {proxyid: getTypeId(element)};
$(document).trigger(ltiEvents.START_EXTERNAL_REGISTRATION, data);
};
/**
* Sets up the listeners for user interaction on this tool type card.
*
* @method registerEventListeners
* @private
* @param {JQuery} element jQuery object representing the tool card.
*/
var registerEventListeners = function(element) {
var deleteButton = getDeleteButton(element);
deleteButton.click(function(e) {
e.preventDefault();
deleteType(element);
});
deleteButton.keypress(function(e) {
if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
e.preventDefault();
deleteButton.click();
}
}
});
var activateButton = getActivateButton(element);
activateButton.click(function(e) {
e.preventDefault();
activateToolType(element);
});
activateButton.keypress(function(e) {
if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
e.preventDefault();
activateButton.click();
}
}
});
};
return /** @alias module:mod_lti/tool_card_controller */ {
/**
* Initialise this module.
*
* @param {JQuery} element jQuery object representing the tool card.
*/
init: function(element) {
registerEventListeners(element);
}
};
});
+169
View File
@@ -0,0 +1,169 @@
// 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/>.
/**
* Provides an interface for a tool type in the Moodle server.
*
* @module mod_lti/tool_type
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['core/ajax', 'core/notification'], function(ajax, notification) {
return {
/**
* Get a list of tool types from Moodle for the given
* search args.
*
* See also:
* mod/lti/classes/external.php get_tool_types_parameters()
*
* @method query
* @public
* @param {Object} args Search parameters
* @return {Promise} jQuery Deferred object
*/
query: function(args) {
var request = {
methodname: 'mod_lti_get_tool_types',
args: args || {}
};
var promise = ajax.call([request])[0];
promise.fail(notification.exception);
return promise;
},
/**
* Create a tool type in Moodle.
*
* The promise will fail if the URL is not a cartridge, so you must handle the fail result.
*
* See also:
* mod/lti/classes/external.php create_tool_type_parameters()
*
* @method create
* @public
* @param {Object} args Tool type properties
* @return {Promise} jQuery Deferred object
*/
create: function(args) {
var request = {
methodname: 'mod_lti_create_tool_type',
args: args
};
var promise = ajax.call([request])[0];
return promise;
},
/**
* Update a tool type in Moodle.
*
* See also:
* mod/lti/classes/external.php update_tool_type_parameters()
*
* @method update
* @public
* @param {Object} args Tool type properties
* @return {Promise} jQuery Deferred object
*/
update: function(args) {
var request = {
methodname: 'mod_lti_update_tool_type',
args: args
};
var promise = ajax.call([request])[0];
promise.fail(notification.exception);
return promise;
},
/**
* Delete a tool type from Moodle.
*
* @method delete
* @public
* @param {Integer} id Tool type ID
* @return {Promise} jQuery Deferred object
*/
'delete': function(id) {
var request = {
methodname: 'mod_lti_delete_tool_type',
args: {
id: id
}
};
var promise = ajax.call([request])[0];
promise.fail(notification.exception);
return promise;
},
/**
* Get a list of tool types from Moodle for the given
* tool proxy id.
*
* @method query
* @public
* @param {Integer} id Tool type ID
* @return {Promise} jQuery Deferred object
*/
getFromToolProxyId: function(id) {
return this.query({toolproxyid: id});
},
/**
* Check if the given URL is a cartridge URL.
*
* The promise will fail if the URL is unreachable, so you must handle the fail result.
*
* @method isCartridge
* @public
* @param {String} url
* @return {Promise} jQuery Deferred object
*/
isCartridge: function(url) {
var request = {
methodname: 'mod_lti_is_cartridge',
args: {
url: url
}
};
var promise = ajax.call([request])[0];
return promise;
},
/**
* Tool type constants.
*/
constants: {
state: {
configured: 1,
pending: 2,
rejected: 3
},
}
};
});
+67
View File
@@ -0,0 +1,67 @@
// 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/>.
/**
* Provides an interface for external tools in the Moodle server.
*
* @module mod_lti/tool_types_and_proxies
* @class tool_types_and_proxies
* @copyright 2020 Andrew Madden <andrewmadden@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.0
*/
import ajax from 'core/ajax';
/**
* Get a list of LTI tool types and tool proxies from Moodle for the given
* search args.
*
* See also:
* mod/lti/classes/external.php get_tool_types_and_proxies()
*
* @method query
* @public
* @param {Object} args Search parameters
* @return {Promise} Promise that will be resolved when the ajax call returns.
*/
export const query = (args) => {
const request = {
methodname: 'mod_lti_get_tool_types_and_proxies',
args: args || {}
};
return ajax.call([request])[0];
};
/**
* Get a count of LTI tool types and tool proxies from Moodle for the given
* search args.
*
* See also:
* mod/lti/classes/external.php get_tool_types_and_proxies_count()
*
* @method count
* @public
* @param {Object} args Search parameters
* @return {Promise} Promise that will be resolved when the ajax call returns.
*/
export const count = (args) => {
const request = {
methodname: 'mod_lti_get_tool_types_and_proxies_count',
args: args || {}
};
return ajax.call([request])[0];
};
+171
View File
@@ -0,0 +1,171 @@
<?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 responds to a login authentication request
*
* @package mod_lti
* @copyright 2019 Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/lti/locallib.php');
global $_POST, $_SERVER;
if (!isloggedin() && empty($_POST['repost'])) {
header_remove("Set-Cookie");
$PAGE->set_pagelayout('popup');
$PAGE->set_context(context_system::instance());
$output = $PAGE->get_renderer('mod_lti');
$page = new \mod_lti\output\repost_crosssite_page($_SERVER['REQUEST_URI'], $_POST);
echo $output->header();
echo $output->render($page);
echo $output->footer();
return;
}
$scope = optional_param('scope', '', PARAM_TEXT);
$responsetype = optional_param('response_type', '', PARAM_TEXT);
$clientid = optional_param('client_id', '', PARAM_TEXT);
$redirecturi = optional_param('redirect_uri', '', PARAM_URL);
$loginhint = optional_param('login_hint', '', PARAM_TEXT);
$ltimessagehintenc = optional_param('lti_message_hint', '', PARAM_TEXT);
$state = optional_param('state', '', PARAM_TEXT);
$responsemode = optional_param('response_mode', '', PARAM_TEXT);
$nonce = optional_param('nonce', '', PARAM_TEXT);
$prompt = optional_param('prompt', '', PARAM_TEXT);
$ok = !empty($scope) && !empty($responsetype) && !empty($clientid) &&
!empty($redirecturi) && !empty($loginhint) &&
!empty($nonce);
if (!$ok) {
$error = 'invalid_request';
}
$ltimessagehint = json_decode($ltimessagehintenc);
$ok = $ok && isset($ltimessagehint->launchid);
if (!$ok) {
$error = 'invalid_request';
$desc = 'No launch id in LTI hint';
}
if ($ok && ($scope !== 'openid')) {
$ok = false;
$error = 'invalid_scope';
}
if ($ok && ($responsetype !== 'id_token')) {
$ok = false;
$error = 'unsupported_response_type';
}
if ($ok) {
$launchid = $ltimessagehint->launchid;
list($courseid, $typeid, $id, $messagetype, $foruserid, $titleb64, $textb64) = explode(',', $SESSION->$launchid, 7);
unset($SESSION->$launchid);
$config = lti_get_type_type_config($typeid);
$ok = ($clientid === $config->lti_clientid);
if (!$ok) {
$error = 'unauthorized_client';
}
}
if ($ok && ($loginhint !== $USER->id)) {
$ok = false;
$error = 'access_denied';
}
// If we're unable to load up config; we cannot trust the redirect uri for POSTing to.
if (empty($config)) {
throw new moodle_exception('invalidrequest', 'error');
} else {
$uris = array_map("trim", explode("\n", $config->lti_redirectionuris));
if (!in_array($redirecturi, $uris)) {
throw new moodle_exception('invalidrequest', 'error');
}
}
if ($ok) {
if (isset($responsemode)) {
$ok = ($responsemode === 'form_post');
if (!$ok) {
$error = 'invalid_request';
$desc = 'Invalid response_mode';
}
} else {
$ok = false;
$error = 'invalid_request';
$desc = 'Missing response_mode';
}
}
if ($ok && !empty($prompt) && ($prompt !== 'none')) {
$ok = false;
$error = 'invalid_request';
$desc = 'Invalid prompt';
}
if ($ok) {
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
if ($id) {
$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
$context = context_module::instance($cm->id);
require_login($course, true, $cm);
require_capability('mod/lti:view', $context);
$lti = $DB->get_record('lti', array('id' => $cm->instance), '*', MUST_EXIST);
$lti->cmid = $cm->id;
list($endpoint, $params) = lti_get_launch_data($lti, $nonce, $messagetype, $foruserid);
} else {
require_login($course);
$context = context_course::instance($courseid);
require_capability('moodle/course:manageactivities', $context);
require_capability('mod/lti:addcoursetool', $context);
// Set the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
$returnurlparams = [
'course' => $courseid,
'id' => $typeid,
'sesskey' => sesskey()
];
$returnurl = new \moodle_url('/mod/lti/contentitem_return.php', $returnurlparams);
// Prepare the request.
$title = base64_decode($titleb64);
$text = base64_decode($textb64);
$request = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text,
[], [], false, true, false, false, false, $nonce);
$endpoint = $request->url;
$params = $request->params;
}
} else {
$params['error'] = $error;
if (!empty($desc)) {
$params['error_description'] = $desc;
}
}
if (isset($state)) {
$params['state'] = $state;
}
unset($SESSION->lti_message_hint);
$r = '<form action="' . $redirecturi . "\" name=\"ltiAuthForm\" id=\"ltiAuthForm\" " .
"method=\"post\" enctype=\"application/x-www-form-urlencoded\">\n";
if (!empty($params)) {
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.ltiAuthForm.submit();\n" .
"//]]>\n" .
"</script>\n";
echo $r;
+148
View File
@@ -0,0 +1,148 @@
<?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/>.
/**
* 1.9 to 2.0 backup format converter. (Also currently used in common cartridge import process)
*
* @package mod_lti
* @copyright Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Darko Miletic
*/
defined('MOODLE_INTERNAL') || die();
class moodle1_mod_lti_handler extends moodle1_mod_handler {
/** @var moodle1_file_manager */
protected $fileman = null;
/** @var int cmid */
protected $moduleid = null;
/**
* Declare the paths in moodle.xml we are able to convert
*
* The method returns list of {@link convert_path} instances.
* For each path returned, the corresponding conversion method must be
* defined.
*
* Note that the path /MOODLE_BACKUP/COURSE/MODULES/MOD/LTI does not
* actually exist in the file. The last element with the module name was
* appended by the moodle1_converter class.
*
* @return array of {@link convert_path} instances
*/
public function get_paths() {
return array(
new convert_path(
'basiclti', '/MOODLE_BACKUP/COURSE/MODULES/MOD/LTI'
)
);
}
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/LTI
* data available
*/
public function process_basiclti($data) {
global $DB;
// Get the course module id and context id.
$instanceid = $data['id'];
$cminfo = $this->get_cminfo($instanceid);
$this->moduleid = $cminfo['id'];
$contextid = $this->converter->get_contextid(CONTEXT_MODULE, $this->moduleid);
// Get a fresh new file manager for this instance.
$this->fileman = $this->converter->get_file_manager($contextid, 'mod_lti');
// Convert course files embedded into the intro.
$this->fileman->filearea = 'intro';
$this->fileman->itemid = 0;
$data['intro'] = moodle1_converter::migrate_referenced_files($data['intro'], $this->fileman);
// Start writing assignment.xml.
$this->open_xml_writer("activities/lti_{$this->moduleid}/lti.xml");
$this->xmlwriter->begin_tag('activity', array('id' => $instanceid, 'moduleid' => $this->moduleid,
'modulename' => 'lti', 'contextid' => $contextid));
$this->xmlwriter->begin_tag('lti', array('id' => $instanceid));
$ignorefields = array('id', 'modtype');
if (!$DB->record_exists('lti_types', array('id' => $data['typeid']))) {
$ntypeid = false;
$toolurls = $DB->get_records_select(
'lti_types_config',
"name = 'toolurl' AND " . $DB->sql_compare_text('value', 256) . ' = ' . $DB->sql_compare_text('?', 256),
[$data['toolurl']],
'',
'id, value'
);
foreach ($toolurls as $id => $value) {
if ($value == $data['toolurl']) {
$ntypeid = $id;
break;
}
}
if ($ntypeid === false) {
$ntypeid = $DB->get_field('lti_types_config',
'typeid',
array(),
IGNORE_MULTIPLE);
}
if ($ntypeid === false) {
$ntypeid = 0;
}
$data['typeid'] = $ntypeid;
}
if (empty($data['servicesalt'])) {
$data['servicesalt'] = uniqid('', true);
}
foreach ($data as $field => $value) {
if (!in_array($field, $ignorefields)) {
$this->xmlwriter->full_tag($field, $value);
}
}
return $data;
}
/**
* This is executed when we reach the closing </MOD> tag of our 'lti' path
*/
public function on_basiclti_end() {
// Finish writing basiclti.xml.
$this->xmlwriter->end_tag('lti');
$this->xmlwriter->end_tag('activity');
$this->close_xml_writer();
// Write inforef.xml.
$this->open_xml_writer("activities/lti_{$this->moduleid}/inforef.xml");
$this->xmlwriter->begin_tag('inforef');
$this->xmlwriter->begin_tag('fileref');
foreach ($this->fileman->get_fileids() as $fileid) {
$this->write_xml('file', array('id' => $fileid));
}
$this->xmlwriter->end_tag('fileref');
$this->xmlwriter->end_tag('inforef');
$this->close_xml_writer();
}
}
@@ -0,0 +1,91 @@
<?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.
/**
* Defines backup_lti_activity_task class
*
* @package mod_lti
* @category backup
* @copyright 2009 Marc Alier <marc.alier@upc.edu>, Jordi Piguillem, Nikolas Galanis
* @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu
* @author Marc Alier
* @author Jordi Piguillem
* @author Nikolas Galanis
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot . '/mod/lti/backup/moodle2/backup_lti_stepslib.php');
/**
* Provides the steps to perform one complete backup of the LTI instance
*/
class backup_lti_activity_task extends backup_activity_task {
/**
* No specific settings for this activity
*/
protected function define_my_settings() {
}
/**
* Defines a backup step to store the instance data in the lti.xml file
*/
protected function define_my_steps() {
$this->add_step(new backup_lti_activity_structure_step('lti_structure', 'lti.xml'));
}
/**
* Encodes URLs to the index.php and view.php scripts
*
* @param string $content some HTML text that eventually contains URLs to the activity instance scripts
* @return string the content with the URLs encoded
*/
public static function encode_content_links($content) {
global $CFG;
$base = preg_quote($CFG->wwwroot, "/");
// Link to the list of basiclti tools.
$search = "/(".$base."\/mod\/lti\/index.php\?id\=)([0-9]+)/";
$content = preg_replace($search, '$@LTIINDEX*$2@$', $content);
// Link to basiclti view by moduleid.
$search = "/(".$base."\/mod\/lti\/view.php\?id\=)([0-9]+)/";
$content = preg_replace($search, '$@LTIVIEWBYID*$2@$', $content);
return $content;
}
}
@@ -0,0 +1,265 @@
<?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 all the backup steps that will be used
* by the backup_lti_activity_task
*
* @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
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* Define the complete assignment structure for backup, with file and id annotations
*/
class backup_lti_activity_structure_step extends backup_activity_structure_step {
/**
* Defines structure of activity backup
* @return backup_nested_element
*/
protected function define_structure() {
global $DB;
// To know if we are including userinfo.
$userinfo = $this->get_setting_value('userinfo');
// Define each element separated.
$lti = new backup_nested_element('lti', array('id'), array(
'name',
'intro',
'introformat',
'timecreated',
'timemodified',
'typeid',
'toolurl',
'securetoolurl',
'preferheight',
'launchcontainer',
'instructorchoicesendname',
'instructorchoicesendemailaddr',
'instructorchoiceacceptgrades',
'instructorchoiceallowroster',
'instructorchoiceallowsetting',
'grade',
'instructorcustomparameters',
'debuglaunch',
'showtitlelaunch',
'showdescriptionlaunch',
'icon',
'secureicon',
new encrypted_final_element('resourcekey'),
new encrypted_final_element('password'),
)
);
$ltitype = new backup_nested_element('ltitype', array('id'), array(
'name',
'baseurl',
'tooldomain',
'state',
'course',
'coursevisible',
'ltiversion',
'clientid',
'toolproxyid',
'enabledcapability',
'parameter',
'icon',
'secureicon',
'createdby',
'timecreated',
'timemodified',
'description'
)
);
$ltitypesconfigs = new backup_nested_element('ltitypesconfigs');
$ltitypesconfig = new backup_nested_element('ltitypesconfig', array('id'), array(
'name',
'value',
)
);
$ltitypesconfigencrypted = new backup_nested_element('ltitypesconfigencrypted', array('id'), array(
'name',
new encrypted_final_element('value'),
)
);
$ltitoolproxy = new backup_nested_element('ltitoolproxy', array('id'));
$ltitoolsettings = new backup_nested_element('ltitoolsettings');
$ltitoolsetting = new backup_nested_element('ltitoolsetting', array('id'), array(
'settings',
'timecreated',
'timemodified',
)
);
$ltisubmissions = new backup_nested_element('ltisubmissions');
$ltisubmission = new backup_nested_element('ltisubmission', array('id'), array(
'userid',
'datesubmitted',
'dateupdated',
'gradepercent',
'originalgrade',
'launchid',
'state'
));
$lticoursevisible = new backup_nested_element('lticoursevisible', ['id'], [
'typeid',
'courseid',
'coursevisible',
]);
// Build the tree
$lti->add_child($ltitype);
$ltitype->add_child($ltitypesconfigs);
$ltitypesconfigs->add_child($ltitypesconfig);
$ltitypesconfigs->add_child($ltitypesconfigencrypted);
$ltitype->add_child($ltitoolproxy);
$ltitoolproxy->add_child($ltitoolsettings);
$ltitoolsettings->add_child($ltitoolsetting);
$lti->add_child($ltisubmissions);
$ltisubmissions->add_child($ltisubmission);
$lti->add_child($lticoursevisible);
// Define sources.
$ltirecord = $DB->get_record('lti', ['id' => $this->task->get_activityid()]);
$lti->set_source_array([$ltirecord]);
$ltitypedata = $this->retrieve_lti_type($ltirecord);
$ltitype->set_source_array($ltitypedata ? [$ltitypedata] : []);
if (isset($ltitypedata->baseurl)) {
// Add type config values only if the type was backed up. Encrypt password and resourcekey.
$params = [backup_helper::is_sqlparam($ltitypedata->id),
backup_helper::is_sqlparam('password'),
backup_helper::is_sqlparam('resourcekey')];
$ltitypesconfig->set_source_sql("SELECT id, name, value
FROM {lti_types_config}
WHERE typeid = ? AND name <> ? AND name <> ?", $params);
$ltitypesconfigencrypted->set_source_sql("SELECT id, name, value
FROM {lti_types_config}
WHERE typeid = ? AND (name = ? OR name = ?)", $params);
}
if (!empty($ltitypedata->toolproxyid)) {
// If this is LTI 2 tool add settings for the current activity.
$ltitoolproxy->set_source_array([['id' => $ltitypedata->toolproxyid]]);
$ltitoolsetting->set_source_sql("SELECT *
FROM {lti_tool_settings}
WHERE toolproxyid = ? AND course = ? AND coursemoduleid = ?",
[backup_helper::is_sqlparam($ltitypedata->toolproxyid), backup::VAR_COURSEID, backup::VAR_MODID]);
} else {
$ltitoolproxy->set_source_array([]);
}
// All the rest of elements only happen if we are including user info.
if ($userinfo) {
$ltisubmission->set_source_table('lti_submission', array('ltiid' => backup::VAR_ACTIVITYID));
}
$lticoursevisibledata = $this->retrieve_lti_coursevisible($ltirecord);
$lticoursevisible->set_source_array($lticoursevisibledata ? [$lticoursevisibledata] : []);
// Define id annotations
$ltitype->annotate_ids('user', 'createdby');
$ltitype->annotate_ids('course', 'course');
$ltisubmission->annotate_ids('user', 'userid');
// Define file annotations.
$lti->annotate_files('mod_lti', 'intro', null); // This file areas haven't itemid.
// Add support for subplugin structures.
$this->add_subplugin_structure('ltisource', $lti, true);
$this->add_subplugin_structure('ltiservice', $lti, true);
// Return the root element (lti), wrapped into standard activity structure.
return $this->prepare_activity_structure($lti);
}
/**
* Retrieves a record from {lti_type} table associated with the current activity
*
* Information about site tools is not returned because it is insecure to back it up,
* only fields necessary for same-site tool matching are left in the record
*
* @param stdClass $ltirecord record from {lti} table
* @return stdClass|null
*/
protected function retrieve_lti_type($ltirecord) {
global $DB;
if (!$ltirecord->typeid) {
return null;
}
$record = $DB->get_record('lti_types', ['id' => $ltirecord->typeid]);
if ($record && $record->course == SITEID) {
// Site LTI types or registrations are not backed up except for their name (which is visible).
// Predefined course types can be backed up.
$allowedkeys = ['id', 'course', 'name', 'toolproxyid'];
foreach ($record as $key => $value) {
if (!in_array($key, $allowedkeys)) {
$record->$key = null;
}
}
}
return $record;
}
/**
* Retrieves a record from {lti_coursevisible} table associated with the current type
*
* @param stdClass $ltirecord record from {lti} table
* @return mixed
*/
protected function retrieve_lti_coursevisible(stdClass $ltirecord): mixed {
global $DB;
if (!$ltirecord->typeid) {
return null;
}
return $DB->get_record('lti_coursevisible', ['typeid' => $ltirecord->typeid, 'courseid' => $ltirecord->course]);
}
}
@@ -0,0 +1,132 @@
<?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 lti module restore class
*
* @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
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/lti/backup/moodle2/restore_lti_stepslib.php');
/**
* basiclti restore task that provides all the settings and steps to perform one
* complete restore of the activity
*/
class restore_lti_activity_task extends restore_activity_task {
/**
* Define (add) particular settings this activity can have
*/
protected function define_my_settings() {
// No particular settings for this activity.
}
/**
* Define (add) particular steps this activity can have
*/
protected function define_my_steps() {
// Label only has one structure step.
$this->add_step(new restore_lti_activity_structure_step('lti_structure', 'lti.xml'));
}
/**
* Define the contents in the activity that must be
* processed by the link decoder
*/
public static function define_decode_contents() {
$contents = array();
$contents[] = new restore_decode_content('lti', array('intro'), 'lti');
return $contents;
}
/**
* Define the decoding rules for links belonging
* to the activity to be executed by the link decoder
*/
public static function define_decode_rules() {
$rules = array();
$rules[] = new restore_decode_rule('LTIVIEWBYID', '/mod/lti/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('LTIINDEX', '/mod/lti/index.php?id=$1', 'course');
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* basiclti logs. It must return one array
* of {@link restore_log_rule} objects
*/
public static function define_restore_log_rules() {
$rules = array();
$rules[] = new restore_log_rule('lti', 'add', 'view.php?id={course_module}', '{lti}');
$rules[] = new restore_log_rule('lti', 'update', 'view.php?id={course_module}', '{lti}');
$rules[] = new restore_log_rule('lti', 'view', 'view.php?id={course_module}', '{lti}');
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* course logs. It must return one array
* of {@link restore_log_rule} objects
*
* Note this rules are applied when restoring course logs
* by the restore final task, but are defined here at
* activity level. All them are rules not linked to any module instance (cmid = 0)
*/
public static function define_restore_log_rules_for_course() {
$rules = array();
$rules[] = new restore_log_rule('lti', 'view all', 'index.php?id={course}', null);
return $rules;
}
}
@@ -0,0 +1,310 @@
<?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 all the restore steps that will be used
* by the restore_lti_activity_task
*
* @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
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
use mod_lti\local\ltiopenid\registration_helper;
/**
* Structure step to restore one lti activity
*/
class restore_lti_activity_structure_step extends restore_activity_structure_step {
/** @var bool */
protected $newltitype = false;
protected function define_structure() {
$paths = array();
// To know if we are including userinfo.
$userinfo = $this->get_setting_value('userinfo');
$lti = new restore_path_element('lti', '/activity/lti');
$paths[] = $lti;
$paths[] = new restore_path_element('ltitype', '/activity/lti/ltitype');
$paths[] = new restore_path_element('ltitypesconfig', '/activity/lti/ltitype/ltitypesconfigs/ltitypesconfig');
$paths[] = new restore_path_element('ltitypesconfigencrypted',
'/activity/lti/ltitype/ltitypesconfigs/ltitypesconfigencrypted');
$paths[] = new restore_path_element('ltitoolproxy', '/activity/lti/ltitype/ltitoolproxy');
$paths[] = new restore_path_element('ltitoolsetting', '/activity/lti/ltitype/ltitoolproxy/ltitoolsettings/ltitoolsetting');
if ($userinfo) {
$submission = new restore_path_element('ltisubmission', '/activity/lti/ltisubmissions/ltisubmission');
$paths[] = $submission;
}
$paths[] = new restore_path_element('lticoursevisible', '/activity/lti/lticoursevisible');
// Add support for subplugin structures.
$this->add_subplugin_structure('ltisource', $lti);
$this->add_subplugin_structure('ltiservice', $lti);
// Return the paths wrapped into standard activity structure.
return $this->prepare_activity_structure($paths);
}
protected function process_lti($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->course = $this->get_courseid();
$data->servicesalt = uniqid('', true);
// Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
// See MDL-9367.
// Grade used to be a float (whole numbers only), restore as int.
$data->grade = (int) $data->grade;
$data->typeid = 0;
// Try to decrypt resourcekey and password. Null if not possible (DB default).
// Note these fields were originally encrypted on backup using {link @encrypted_final_element}.
$data->resourcekey = isset($data->resourcekey) ? $this->decrypt($data->resourcekey) : null;
$data->password = isset($data->password) ? $this->decrypt($data->password) : null;
$newitemid = $DB->insert_record('lti', $data);
// Immediately after inserting "activity" record, call this.
$this->apply_activity_instance($newitemid);
}
/**
* Process an lti type restore
* @param mixed $data The data from backup XML file
* @return void
*/
protected function process_ltitype($data) {
global $DB, $USER;
$data = (object)$data;
$oldid = $data->id;
if (!empty($data->createdby)) {
$data->createdby = $this->get_mappingid('user', $data->createdby) ?: $USER->id;
}
$courseid = $this->get_courseid();
$data->course = ($this->get_mappingid('course', $data->course) == $courseid) ? $courseid : SITEID;
// Try to find existing lti type with the same properties.
$ltitypeid = $this->find_existing_lti_type($data);
$this->newltitype = false;
if (!$ltitypeid && $data->course == $courseid) {
unset($data->toolproxyid); // Course tools can not use LTI2.
if (!empty($data->clientid)) {
// Need to rebuild clientid to ensure uniqueness.
$data->clientid = registration_helper::get()->new_clientid();
}
$ltitypeid = $DB->insert_record('lti_types', $data);
$this->newltitype = true;
$this->set_mapping('ltitype', $oldid, $ltitypeid);
}
// Add the typeid entry back to LTI module.
$DB->update_record('lti', ['id' => $this->get_new_parentid('lti'), 'typeid' => $ltitypeid]);
}
/**
* Process an lti coursevisible restore
* @param mixed $data The data from backup XML file
* @return void
*/
protected function process_lticoursevisible($data) {
global $DB;
$data = (object)$data;
$data->typeid = $this->get_new_parentid('ltitype');
$data->courseid = $this->get_courseid();
if ($data->typeid) {
$DB->insert_record('lti_coursevisible', $data);
}
}
/**
* Attempts to find existing record in lti_type
* @param stdClass $data
* @return int|null field lti_types.id or null if tool is not found
*/
protected function find_existing_lti_type($data) {
global $DB;
if ($ltitypeid = $this->get_mappingid('ltitype', $data->id)) {
return $ltitypeid;
}
$ltitype = null;
$params = (array)$data;
if ($this->task->is_samesite()) {
// If we are restoring on the same site try to find lti type with the same id.
$sql = 'id = :id AND course = :course';
$sql .= ($data->toolproxyid) ? ' AND toolproxyid = :toolproxyid' : ' AND toolproxyid IS NULL';
if ($DB->record_exists_select('lti_types', $sql, $params)) {
$this->set_mapping('ltitype', $data->id, $data->id);
if ($data->toolproxyid) {
$this->set_mapping('ltitoolproxy', $data->toolproxyid, $data->toolproxyid);
}
return $data->id;
}
}
if ($data->course != $this->get_courseid()) {
// Site tools are not backed up and are not restored.
return null;
}
// Now try to find the same type on the current site available in this course.
// Compare only fields baseurl, course and name, if they are the same we assume it is the same tool.
// LTI2 is not possible in the course so we add "lt.toolproxyid IS NULL" to the query.
$sql = 'SELECT id
FROM {lti_types}
WHERE ' . $DB->sql_compare_text('baseurl', 255) . ' = ' . $DB->sql_compare_text(':baseurl', 255) . ' AND
course = :course AND name = :name AND toolproxyid IS NULL';
if ($ltitype = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE)) {
$this->set_mapping('ltitype', $data->id, $ltitype->id);
return $ltitype->id;
}
return null;
}
/**
* Process an lti config restore
* @param mixed $data The data from backup XML file
*/
protected function process_ltitypesconfig($data) {
global $DB;
$data = (object)$data;
$data->typeid = $this->get_new_parentid('ltitype');
// Only add configuration if the new lti_type was created.
if ($data->typeid && $this->newltitype) {
if ($data->name == 'servicesalt') {
$data->value = uniqid('', true);
}
$DB->insert_record('lti_types_config', $data);
}
}
/**
* Process an lti config restore
* @param mixed $data The data from backup XML file
*/
protected function process_ltitypesconfigencrypted($data) {
global $DB;
$data = (object)$data;
$data->typeid = $this->get_new_parentid('ltitype');
// Only add configuration if the new lti_type was created.
if ($data->typeid && $this->newltitype) {
$data->value = $this->decrypt($data->value);
if (!is_null($data->value)) {
$DB->insert_record('lti_types_config', $data);
}
}
}
/**
* Process a restore of LTI tool registration
* This method is empty because we actually process registration as part of process_ltitype()
* @param mixed $data The data from backup XML file
*/
protected function process_ltitoolproxy($data) {
}
/**
* Process an lti tool registration settings restore (only settings for the current activity)
* @param mixed $data The data from backup XML file
*/
protected function process_ltitoolsetting($data) {
global $DB;
$data = (object)$data;
$data->toolproxyid = $this->get_new_parentid('ltitoolproxy');
if (!$data->toolproxyid) {
return;
}
$data->course = $this->get_courseid();
$data->coursemoduleid = $this->task->get_moduleid();
$DB->insert_record('lti_tool_settings', $data);
}
/**
* Process a submission restore
* @param mixed $data The data from backup XML file
*/
protected function process_ltisubmission($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->ltiid = $this->get_new_parentid('lti');
$data->datesubmitted = $this->apply_date_offset($data->datesubmitted);
$data->dateupdated = $this->apply_date_offset($data->dateupdated);
if ($data->userid > 0) {
$data->userid = $this->get_mappingid('user', $data->userid);
}
$newitemid = $DB->insert_record('lti_submission', $data);
$this->set_mapping('ltisubmission', $oldid, $newitemid);
}
protected function after_execute() {
// Add lti related files, no need to match by itemname (just internally handled context).
$this->add_related_files('mod_lti', 'intro', null);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?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 returns an array of available public keys
*
* @package mod_lti
* @copyright 2019 Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_lti\local\ltiopenid\jwks_helper;
define('NO_DEBUG_DISPLAY', true);
define('NO_MOODLE_COOKIES', true);
require_once(__DIR__ . '/../../config.php');
@header('Content-Type: application/json; charset=utf-8');
echo json_encode(jwks_helper::get_jwks(), JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
@@ -0,0 +1,47 @@
<?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/>.
/**
* Activity base class.
*
* @package mod_lti
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Activity base class.
*
* @package mod_lti
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
/**
* feedback_viewed_events
*
* @return string[]
*/
protected function feedback_viewed_events() {
// Any view after the data graded counts as feedback viewed.
return array('\mod_lti\event\course_module_viewed');
}
}
@@ -0,0 +1,57 @@
<?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/>.
/**
* Cognitive depth indicator - lti.
*
* @package mod_lti
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Cognitive depth indicator - lti.
*
* @package mod_lti
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:cognitivedepth', 'mod_lti');
}
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
public function get_cognitive_depth_level(\cm_info $cm) {
return self::COGNITIVE_LEVEL_3;
}
}
@@ -0,0 +1,56 @@
<?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/>.
/**
* Social breadth indicator - lti.
*
* @package mod_lti
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Social breadth indicator - lti.
*
* @package mod_lti
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:socialbreadth', 'mod_lti');
}
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
public function get_social_breadth_level(\cm_info $cm) {
return self::SOCIAL_LEVEL_2;
}
}
@@ -0,0 +1,40 @@
<?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/>.
/**
* The mod_lti instance list viewed event.
*
* @package mod_lti
* @copyright 2013 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_lti instance list viewed event class.
*
* @package mod_lti
* @since Moodle 2.7
* @copyright 2013 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
// No need for any code here as everything is handled by the parent class.
}
@@ -0,0 +1,51 @@
<?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/>.
/**
* The mod_lti course module viewed event.
*
* @package mod_lti
* @copyright 2013 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_lti course module viewed event class.
*
* @package mod_lti
* @since Moodle 2.7
* @copyright 2013 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_viewed extends \core\event\course_module_viewed {
/**
* Init method.
*/
protected function init() {
$this->data['objecttable'] = 'lti';
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
public static function get_objectid_mapping() {
return array('db' => 'lti', 'restore' => 'lti');
}
}
@@ -0,0 +1,90 @@
<?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/>.
/**
* The mod_lti unknown service api called event.
*
* @package mod_lti
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_lti unknown service api called event class.
*
* Event for when something happens with an unknown lti service API call.
*
* @package mod_lti
* @since Moodle 2.6
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class unknown_service_api_called extends \core\event\base {
/** @var \stdClass Data to be used by event observers. */
protected $eventdata;
/**
* Sets custom data used by event observers.
*
* @param \stdClass $data
*/
public function set_message_data(\stdClass $data) {
$this->eventdata = $data;
}
/**
* Returns custom data for event observers.
*
* @return \stdClass
*/
public function get_message_data() {
if ($this->is_restored()) {
throw new \coding_exception('Function get_message_data() can not be used on restored events.');
}
return $this->eventdata;
}
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->context = \context_system::instance();
}
/**
* Returns localised description of what happened.
*
* @return string
*/
public function get_description() {
return 'An unknown call to a service api was made.';
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('ltiunknownserviceapicall', 'mod_lti');
}
}
+993
View File
@@ -0,0 +1,993 @@
<?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/>.
/**
* External tool module external API
*
* @package mod_lti
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
use core_course\external\helper_for_get_mods_by_courses;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\util;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot . '/mod/lti/lib.php');
require_once($CFG->dirroot . '/mod/lti/locallib.php');
/**
* External tool module external functions
*
* @package mod_lti
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
class mod_lti_external extends external_api {
/**
* Returns structure be used for returning a tool type from a web service.
*
* @return external_function_parameters
* @since Moodle 3.1
*/
private static function tool_type_return_structure() {
return new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'Tool type id'),
'name' => new external_value(PARAM_NOTAGS, 'Tool type name'),
'description' => new external_value(PARAM_NOTAGS, 'Tool type description'),
'platformid' => new external_value(PARAM_TEXT, 'Platform ID'),
'clientid' => new external_value(PARAM_TEXT, 'Client ID'),
'deploymentid' => new external_value(PARAM_INT, 'Deployment ID'),
'urls' => new external_single_structure(
array(
'icon' => new external_value(PARAM_URL, 'Tool type icon URL'),
'edit' => new external_value(PARAM_URL, 'Tool type edit URL'),
'course' => new external_value(PARAM_URL, 'Tool type edit URL', VALUE_OPTIONAL),
'publickeyset' => new external_value(PARAM_URL, 'Public Keyset URL'),
'accesstoken' => new external_value(PARAM_URL, 'Access Token URL'),
'authrequest' => new external_value(PARAM_URL, 'Authorisation Request URL'),
)
),
'state' => new external_single_structure(
array(
'text' => new external_value(PARAM_TEXT, 'Tool type state name string'),
'pending' => new external_value(PARAM_BOOL, 'Is the state pending'),
'configured' => new external_value(PARAM_BOOL, 'Is the state configured'),
'rejected' => new external_value(PARAM_BOOL, 'Is the state rejected'),
'unknown' => new external_value(PARAM_BOOL, 'Is the state unknown'),
)
),
'hascapabilitygroups' => new external_value(PARAM_BOOL, 'Indicate if capabilitygroups is populated'),
'capabilitygroups' => new external_multiple_structure(
new external_value(PARAM_TEXT, 'Tool type capability groups enabled'),
'Array of capability groups', VALUE_DEFAULT, array()
),
'courseid' => new external_value(PARAM_INT, 'Tool type course', VALUE_DEFAULT, 0),
'instanceids' => new external_multiple_structure(
new external_value(PARAM_INT, 'LTI instance ID'),
'IDs for the LTI instances using this type', VALUE_DEFAULT, array()
),
'instancecount' => new external_value(PARAM_INT, 'The number of times this tool is being used')
), 'Tool'
);
}
/**
* Returns description of a tool proxy
*
* @return external_function_parameters
* @since Moodle 3.1
*/
private static function tool_proxy_return_structure() {
return new external_function_parameters(
array(
'id' => new external_value(PARAM_INT, 'Tool proxy id'),
'name' => new external_value(PARAM_TEXT, 'Tool proxy name'),
'regurl' => new external_value(PARAM_URL, 'Tool proxy registration URL'),
'state' => new external_value(PARAM_INT, 'Tool proxy state'),
'guid' => new external_value(PARAM_TEXT, 'Tool proxy globally unique identifier'),
'secret' => new external_value(PARAM_TEXT, 'Tool proxy shared secret'),
'vendorcode' => new external_value(PARAM_TEXT, 'Tool proxy consumer code'),
'capabilityoffered' => new external_value(PARAM_TEXT, 'Tool proxy capabilities offered'),
'serviceoffered' => new external_value(PARAM_TEXT, 'Tool proxy services offered'),
'toolproxy' => new external_value(PARAM_TEXT, 'Tool proxy'),
'timecreated' => new external_value(PARAM_INT, 'Tool proxy time created'),
'timemodified' => new external_value(PARAM_INT, 'Tool proxy modified'),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function get_tool_proxies_parameters() {
return new external_function_parameters(
array(
'orphanedonly' => new external_value(PARAM_BOOL, 'Orphaned tool types only', VALUE_DEFAULT, 0)
)
);
}
/**
* Returns the tool types.
*
* @param bool $orphanedonly Retrieve only tool proxies that do not have a corresponding tool type
* @return array of tool types
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function get_tool_proxies($orphanedonly) {
$params = self::validate_parameters(self::get_tool_proxies_parameters(),
array(
'orphanedonly' => $orphanedonly
));
$orphanedonly = $params['orphanedonly'];
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
return lti_get_tool_proxies($orphanedonly);
}
/**
* Returns description of method result value.
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function get_tool_proxies_returns() {
return new external_multiple_structure(
self::tool_proxy_return_structure()
);
}
/**
* Returns description of method parameters.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_tool_launch_data_parameters() {
return new external_function_parameters(
array(
'toolid' => new external_value(PARAM_INT, 'external tool instance id')
)
);
}
/**
* Return the launch data for a given external tool.
*
* @param int $toolid the external tool instance id
* @return array of warnings and launch data
* @since Moodle 3.0
* @throws moodle_exception
*/
public static function get_tool_launch_data($toolid) {
global $DB, $CFG;
require_once($CFG->dirroot . '/mod/lti/lib.php');
$params = self::validate_parameters(self::get_tool_launch_data_parameters(),
array(
'toolid' => $toolid
));
$warnings = array();
// Request and permission validation.
$lti = $DB->get_record('lti', array('id' => $params['toolid']), '*', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($lti, 'lti');
$context = context_module::instance($cm->id);
self::validate_context($context);
require_capability('mod/lti:view', $context);
$lti->cmid = $cm->id;
list($endpoint, $parms) = lti_get_launch_data($lti);
$parameters = array();
foreach ($parms as $name => $value) {
$parameters[] = array(
'name' => $name,
'value' => $value
);
}
$result = array();
$result['endpoint'] = $endpoint;
$result['parameters'] = $parameters;
$result['warnings'] = $warnings;
return $result;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.0
*/
public static function get_tool_launch_data_returns() {
return new external_single_structure(
array(
'endpoint' => new external_value(PARAM_RAW, 'Endpoint URL'), // Using PARAM_RAW as is defined in the module.
'parameters' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_NOTAGS, 'Parameter name'),
'value' => new external_value(PARAM_RAW, 'Parameter value')
)
)
),
'warnings' => new external_warnings()
)
);
}
/**
* Describes the parameters for get_ltis_by_courses.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_ltis_by_courses_parameters() {
return new external_function_parameters (
array(
'courseids' => new external_multiple_structure(
new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array()
),
)
);
}
/**
* Returns a list of external tools in a provided list of courses,
* if no list is provided all external tools that the user can view will be returned.
*
* @param array $courseids the course ids
* @return array the lti details
* @since Moodle 3.0
*/
public static function get_ltis_by_courses($courseids = array()) {
global $CFG;
$returnedltis = array();
$warnings = array();
$params = self::validate_parameters(self::get_ltis_by_courses_parameters(), array('courseids' => $courseids));
$mycourses = array();
if (empty($params['courseids'])) {
$mycourses = enrol_get_my_courses();
$params['courseids'] = array_keys($mycourses);
}
// Ensure there are courseids to loop through.
if (!empty($params['courseids'])) {
list($courses, $warnings) = util::validate_courses($params['courseids'], $mycourses);
// Get the ltis in this course, this function checks users visibility permissions.
// We can avoid then additional validate_context calls.
$ltis = get_all_instances_in_courses("lti", $courses);
foreach ($ltis as $lti) {
$context = context_module::instance($lti->coursemodule);
// Entry to return.
$module = helper_for_get_mods_by_courses::standard_coursemodule_element_values(
$lti, 'mod_lti', 'moodle/course:manageactivities', 'mod/lti:view');
$viewablefields = [];
if (has_capability('mod/lti:view', $context)) {
$viewablefields = array('launchcontainer', 'showtitlelaunch', 'showdescriptionlaunch', 'icon', 'secureicon');
}
// Check additional permissions for returning optional private settings.
if (has_capability('moodle/course:manageactivities', $context)) {
$additionalfields = array('timecreated', 'timemodified', 'typeid', 'toolurl', 'securetoolurl',
'instructorchoicesendname', 'instructorchoicesendemailaddr', 'instructorchoiceallowroster',
'instructorchoiceallowsetting', 'instructorcustomparameters', 'instructorchoiceacceptgrades', 'grade',
'resourcekey', 'password', 'debuglaunch', 'servicesalt');
$viewablefields = array_merge($viewablefields, $additionalfields);
}
foreach ($viewablefields as $field) {
$module[$field] = $lti->{$field};
}
$returnedltis[] = $module;
}
}
$result = array();
$result['ltis'] = $returnedltis;
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the get_ltis_by_courses return value.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function get_ltis_by_courses_returns() {
return new external_single_structure(
array(
'ltis' => new external_multiple_structure(
new external_single_structure(array_merge(
helper_for_get_mods_by_courses::standard_coursemodule_elements_returns(true),
[
'timecreated' => new external_value(PARAM_INT, 'Time of creation', VALUE_OPTIONAL),
'timemodified' => new external_value(PARAM_INT, 'Time of last modification', VALUE_OPTIONAL),
'typeid' => new external_value(PARAM_INT, 'Type id', VALUE_OPTIONAL),
'toolurl' => new external_value(PARAM_URL, 'Tool url', VALUE_OPTIONAL),
'securetoolurl' => new external_value(PARAM_RAW, 'Secure tool url', VALUE_OPTIONAL),
'instructorchoicesendname' => new external_value(PARAM_TEXT, 'Instructor choice send name',
VALUE_OPTIONAL),
'instructorchoicesendemailaddr' => new external_value(PARAM_INT, 'instructor choice send mail address',
VALUE_OPTIONAL),
'instructorchoiceallowroster' => new external_value(PARAM_INT, 'Instructor choice allow roster',
VALUE_OPTIONAL),
'instructorchoiceallowsetting' => new external_value(PARAM_INT, 'Instructor choice allow setting',
VALUE_OPTIONAL),
'instructorcustomparameters' => new external_value(PARAM_RAW, 'instructor custom parameters',
VALUE_OPTIONAL),
'instructorchoiceacceptgrades' => new external_value(PARAM_INT, 'instructor choice accept grades',
VALUE_OPTIONAL),
'grade' => new external_value(PARAM_INT, 'Enable grades', VALUE_OPTIONAL),
'launchcontainer' => new external_value(PARAM_INT, 'Launch container mode', VALUE_OPTIONAL),
'resourcekey' => new external_value(PARAM_RAW, 'Resource key', VALUE_OPTIONAL),
'password' => new external_value(PARAM_RAW, 'Shared secret', VALUE_OPTIONAL),
'debuglaunch' => new external_value(PARAM_INT, 'Debug launch', VALUE_OPTIONAL),
'showtitlelaunch' => new external_value(PARAM_INT, 'Show title launch', VALUE_OPTIONAL),
'showdescriptionlaunch' => new external_value(PARAM_INT, 'Show description launch', VALUE_OPTIONAL),
'servicesalt' => new external_value(PARAM_RAW, 'Service salt', VALUE_OPTIONAL),
'icon' => new external_value(PARAM_URL, 'Alternative icon URL', VALUE_OPTIONAL),
'secureicon' => new external_value(PARAM_URL, 'Secure icon URL', VALUE_OPTIONAL),
]
), 'Tool')
),
'warnings' => new external_warnings(),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function view_lti_parameters() {
return new external_function_parameters(
array(
'ltiid' => new external_value(PARAM_INT, 'lti instance id')
)
);
}
/**
* Trigger the course module viewed event and update the module completion status.
*
* @param int $ltiid the lti instance id
* @return array of warnings and status result
* @since Moodle 3.0
* @throws moodle_exception
*/
public static function view_lti($ltiid) {
global $DB;
$params = self::validate_parameters(self::view_lti_parameters(),
array(
'ltiid' => $ltiid
));
$warnings = array();
// Request and permission validation.
$lti = $DB->get_record('lti', array('id' => $params['ltiid']), '*', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($lti, 'lti');
$context = context_module::instance($cm->id);
self::validate_context($context);
require_capability('mod/lti:view', $context);
// Trigger course_module_viewed event and completion.
lti_view($lti, $course, $cm, $context);
$result = array();
$result['status'] = true;
$result['warnings'] = $warnings;
return $result;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.0
*/
public static function view_lti_returns() {
return new external_single_structure(
array(
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function create_tool_proxy_parameters() {
return new external_function_parameters(
array(
'name' => new external_value(PARAM_TEXT, 'Tool proxy name', VALUE_DEFAULT, ''),
'regurl' => new external_value(PARAM_URL, 'Tool proxy registration URL'),
'capabilityoffered' => new external_multiple_structure(
new external_value(PARAM_TEXT, 'Tool proxy capabilities offered'),
'Array of capabilities', VALUE_DEFAULT, array()
),
'serviceoffered' => new external_multiple_structure(
new external_value(PARAM_TEXT, 'Tool proxy services offered'),
'Array of services', VALUE_DEFAULT, array()
)
)
);
}
/**
* Creates a new tool proxy
*
* @param string $name Tool proxy name
* @param string $registrationurl Registration url
* @param string[] $capabilityoffered List of capabilities this tool proxy should be offered
* @param string[] $serviceoffered List of services this tool proxy should be offered
* @return object The new tool proxy
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function create_tool_proxy($name, $registrationurl, $capabilityoffered, $serviceoffered) {
$params = self::validate_parameters(self::create_tool_proxy_parameters(),
array(
'name' => $name,
'regurl' => $registrationurl,
'capabilityoffered' => $capabilityoffered,
'serviceoffered' => $serviceoffered
));
$name = $params['name'];
$regurl = $params['regurl'];
$capabilityoffered = $params['capabilityoffered'];
$serviceoffered = $params['serviceoffered'];
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
// Can't create duplicate proxies with the same URL.
$duplicates = lti_get_tool_proxies_from_registration_url($registrationurl);
if (!empty($duplicates)) {
throw new moodle_exception('duplicateregurl', 'mod_lti');
}
$config = new stdClass();
$config->lti_registrationurl = $registrationurl;
if (!empty($name)) {
$config->lti_registrationname = $name;
}
if (!empty($capabilityoffered)) {
$config->lti_capabilities = $capabilityoffered;
}
if (!empty($serviceoffered)) {
$config->lti_services = $serviceoffered;
}
$id = lti_add_tool_proxy($config);
$toolproxy = lti_get_tool_proxy($id);
// Pending makes more sense than configured as the first state, since
// the next step is to register, which requires the state be pending.
$toolproxy->state = LTI_TOOL_PROXY_STATE_PENDING;
lti_update_tool_proxy($toolproxy);
return $toolproxy;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function create_tool_proxy_returns() {
return self::tool_proxy_return_structure();
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function delete_tool_proxy_parameters() {
return new external_function_parameters(
array(
'id' => new external_value(PARAM_INT, 'Tool proxy id'),
)
);
}
/**
* Trigger the course module viewed event and update the module completion status.
*
* @param int $id the lti instance id
* @return object The tool proxy
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function delete_tool_proxy($id) {
$params = self::validate_parameters(self::delete_tool_proxy_parameters(),
array(
'id' => $id,
));
$id = $params['id'];
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
$toolproxy = lti_get_tool_proxy($id);
lti_delete_tool_proxy($id);
return $toolproxy;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function delete_tool_proxy_returns() {
return self::tool_proxy_return_structure();
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_tool_proxy_registration_request_parameters() {
return new external_function_parameters(
array(
'id' => new external_value(PARAM_INT, 'Tool proxy id'),
)
);
}
/**
* Returns the registration request for a tool proxy.
*
* @param int $id the lti instance id
* @return array of registration parameters
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function get_tool_proxy_registration_request($id) {
$params = self::validate_parameters(self::get_tool_proxy_registration_request_parameters(),
array(
'id' => $id,
));
$id = $params['id'];
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
$toolproxy = lti_get_tool_proxy($id);
return lti_build_registration_request($toolproxy);
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function get_tool_proxy_registration_request_returns() {
return new external_function_parameters(
array(
'lti_message_type' => new external_value(PARAM_ALPHANUMEXT, 'LTI message type'),
'lti_version' => new external_value(PARAM_ALPHANUMEXT, 'LTI version'),
'reg_key' => new external_value(PARAM_TEXT, 'Tool proxy registration key'),
'reg_password' => new external_value(PARAM_TEXT, 'Tool proxy registration password'),
'reg_url' => new external_value(PARAM_TEXT, 'Tool proxy registration url'),
'tc_profile_url' => new external_value(PARAM_URL, 'Tool consumers profile URL'),
'launch_presentation_return_url' => new external_value(PARAM_URL, 'URL to redirect on registration completion'),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function get_tool_types_parameters() {
return new external_function_parameters(
array(
'toolproxyid' => new external_value(PARAM_INT, 'Tool proxy id', VALUE_DEFAULT, 0)
)
);
}
/**
* Returns the tool types.
*
* @param int $toolproxyid The tool proxy id
* @return array of tool types
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function get_tool_types($toolproxyid) {
global $PAGE;
$params = self::validate_parameters(self::get_tool_types_parameters(),
array(
'toolproxyid' => $toolproxyid
));
$toolproxyid = $params['toolproxyid'];
$types = array();
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
if (!empty($toolproxyid)) {
$types = lti_get_lti_types_from_proxy_id($toolproxyid);
} else {
$types = lti_get_lti_types();
}
return array_map("serialise_tool_type", array_values($types));
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function get_tool_types_returns() {
return new external_multiple_structure(
self::tool_type_return_structure()
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function create_tool_type_parameters() {
return new external_function_parameters(
array(
'cartridgeurl' => new external_value(PARAM_URL, 'URL to cardridge to load tool information', VALUE_DEFAULT, ''),
'key' => new external_value(PARAM_TEXT, 'Consumer key', VALUE_DEFAULT, ''),
'secret' => new external_value(PARAM_TEXT, 'Shared secret', VALUE_DEFAULT, ''),
)
);
}
/**
* Creates a tool type.
*
* @param string $cartridgeurl Url of the xml cartridge representing the LTI tool
* @param string $key The consumer key to identify this consumer
* @param string $secret The secret
* @return array created tool type
* @since Moodle 3.1
* @throws moodle_exception If the tool type could not be created
*/
public static function create_tool_type($cartridgeurl, $key, $secret) {
$params = self::validate_parameters(self::create_tool_type_parameters(),
array(
'cartridgeurl' => $cartridgeurl,
'key' => $key,
'secret' => $secret
));
$cartridgeurl = $params['cartridgeurl'];
$key = $params['key'];
$secret = $params['secret'];
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
$id = null;
if (!empty($cartridgeurl)) {
$type = new stdClass();
$data = new stdClass();
$type->state = LTI_TOOL_STATE_CONFIGURED;
$data->lti_coursevisible = 1;
$data->lti_sendname = LTI_SETTING_DELEGATE;
$data->lti_sendemailaddr = LTI_SETTING_DELEGATE;
$data->lti_acceptgrades = LTI_SETTING_DELEGATE;
$data->lti_forcessl = 0;
if (!empty($key)) {
$data->lti_resourcekey = $key;
}
if (!empty($secret)) {
$data->lti_password = $secret;
}
lti_load_type_from_cartridge($cartridgeurl, $data);
if (empty($data->lti_toolurl)) {
throw new moodle_exception('unabletocreatetooltype', 'mod_lti');
} else {
$id = lti_add_type($type, $data);
}
}
if (!empty($id)) {
$type = lti_get_type($id);
return serialise_tool_type($type);
} else {
throw new moodle_exception('unabletocreatetooltype', 'mod_lti');
}
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function create_tool_type_returns() {
return self::tool_type_return_structure();
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function update_tool_type_parameters() {
return new external_function_parameters(
array(
'id' => new external_value(PARAM_INT, 'Tool type id'),
'name' => new external_value(PARAM_RAW, 'Tool type name', VALUE_DEFAULT, null),
'description' => new external_value(PARAM_RAW, 'Tool type description', VALUE_DEFAULT, null),
'state' => new external_value(PARAM_INT, 'Tool type state', VALUE_DEFAULT, null)
)
);
}
/**
* Update a tool type.
*
* @param int $id The id of the tool type to update
* @param string $name The name of the tool type
* @param string $description The name of the tool type
* @param int $state The state of the tool type
* @return array updated tool type
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function update_tool_type($id, $name, $description, $state) {
$params = self::validate_parameters(self::update_tool_type_parameters(),
array(
'id' => $id,
'name' => $name,
'description' => $description,
'state' => $state,
));
$id = $params['id'];
$name = $params['name'];
$description = $params['description'];
$state = $params['state'];
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
$type = lti_get_type($id);
if (empty($type)) {
throw new moodle_exception('unabletofindtooltype', 'mod_lti', '', array('id' => $id));
}
if (!empty($name)) {
$type->name = $name;
}
if (!empty($description)) {
$type->description = $description;
}
if (!empty($state)) {
// Valid state range.
if (in_array($state, array(1, 2, 3))) {
$type->state = $state;
} else {
throw new moodle_exception("Invalid state: $state - must be 1, 2, or 3");
}
}
lti_update_type($type, new stdClass());
return serialise_tool_type($type);
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function update_tool_type_returns() {
return self::tool_type_return_structure();
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function delete_tool_type_parameters() {
return new external_function_parameters(
array(
'id' => new external_value(PARAM_INT, 'Tool type id'),
)
);
}
/**
* Delete a tool type.
*
* @param int $id The id of the tool type to be deleted
* @return array deleted tool type
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function delete_tool_type($id) {
$params = self::validate_parameters(self::delete_tool_type_parameters(),
array(
'id' => $id,
));
$id = $params['id'];
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
$type = lti_get_type($id);
if (!empty($type)) {
lti_delete_type($id);
// If this is the last type for this proxy then remove the proxy
// as well so that it isn't orphaned.
$types = lti_get_lti_types_from_proxy_id($type->toolproxyid);
if (empty($types)) {
lti_delete_tool_proxy($type->toolproxyid);
}
}
return array('id' => $id);
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function delete_tool_type_returns() {
return new external_function_parameters(
array(
'id' => new external_value(PARAM_INT, 'Tool type id'),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function is_cartridge_parameters() {
return new external_function_parameters(
array(
'url' => new external_value(PARAM_URL, 'Tool url'),
)
);
}
/**
* Determine if the url to a tool is for a cartridge.
*
* @param string $url Url that may or may not be an xml cartridge
* @return bool True if the url is for a cartridge.
* @since Moodle 3.1
* @throws moodle_exception
*/
public static function is_cartridge($url) {
$params = self::validate_parameters(self::is_cartridge_parameters(),
array(
'url' => $url,
));
$url = $params['url'];
$context = context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
$iscartridge = lti_is_cartridge($url);
return array('iscartridge' => $iscartridge);
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function is_cartridge_returns() {
return new external_function_parameters(
array(
'iscartridge' => new external_value(PARAM_BOOL, 'True if the URL is a cartridge'),
)
);
}
}
+81
View File
@@ -0,0 +1,81 @@
<?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/>.
namespace mod_lti\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
/**
* External function to delete a course tool type.
*
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_course_tool_type extends external_api {
/**
* Get parameter definition.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'tooltypeid' => new external_value(PARAM_INT, 'Tool type ID'),
]);
}
/**
* Delete a course tool type.
*
* @param int $tooltypeid the id of the course external tool type.
* @return bool true
* @throws \invalid_parameter_exception if the provided id refers to a site level tool which cannot be deleted.
*/
public static function execute(int $tooltypeid): bool {
['tooltypeid' => $tooltypeid] = self::validate_parameters(self::execute_parameters(), ['tooltypeid' => $tooltypeid]);
global $DB;
$course = (int) $DB->get_field('lti_types', 'course', ['id' => $tooltypeid]);
if ($course == get_site()->id) {
throw new \invalid_parameter_exception('This is a site-level tool and cannot be deleted via this service');
}
$context = \context_course::instance($course);
self::validate_context($context);
require_capability('mod/lti:addcoursetool', $context);
\lti_delete_type($tooltypeid);
return true;
}
/**
* Get service returns definition.
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, 'Success');
}
}
+118
View File
@@ -0,0 +1,118 @@
<?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/>.
namespace mod_lti\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
/**
* External function for fetching all tool types and proxies.
*
* @package mod_lti
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_tool_types_and_proxies extends external_api {
/**
* Get parameter definition for get_tool_types_and_proxies().
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'toolproxyid' => new external_value(
PARAM_INT,
'Tool proxy id',
VALUE_DEFAULT,
0
),
'orphanedonly' => new external_value(
PARAM_BOOL,
'Orphaned tool types only',
VALUE_DEFAULT,
0
),
'limit' => new external_value(
PARAM_INT,
'How many tool types displayed per page',
VALUE_DEFAULT,
60,
NULL_NOT_ALLOWED
),
'offset' => new external_value(
PARAM_INT,
'Current offset of tool elements',
VALUE_DEFAULT,
0,
NULL_NOT_ALLOWED
),
]);
}
/**
* Get data for all tool types and tool proxies.
*
* @param int $toolproxyid The tool proxy id
* @param bool $orphanedonly Whether to get orphaned proxies only.
* @param int $limit How many elements to return if using pagination.
* @param int $offset Which chunk of elements to return is using pagination.
* @return array
*/
public static function execute($toolproxyid, $orphanedonly, $limit, $offset): array {
$params = self::validate_parameters(self::execute_parameters(), [
'toolproxyid' => $toolproxyid,
'orphanedonly' => $orphanedonly,
'limit' => $limit,
'offset' => $offset,
]);
$toolproxyid = $params['toolproxyid'] !== null ? $params['toolproxyid'] : 0;
$orphanedonly = $params['orphanedonly'] !== null ? $params['orphanedonly'] : false;
$limit = $params['limit'] !== null ? $params['limit'] : 0;
$offset = $params['offset'] !== null ? $params['offset'] : 0;
$context = \context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
[$proxies, $types] = lti_get_lti_types_and_proxies($limit, $offset, $orphanedonly, $toolproxyid);
return [
'types' => $types,
'proxies' => $proxies,
'limit' => $limit,
'offset' => $offset,
];
}
/**
* Get return definition for get_tool_types_and_proxies.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'types' => \mod_lti_external::get_tool_types_returns(),
'proxies' => \mod_lti_external::get_tool_proxies_returns(),
'limit' => new external_value(PARAM_INT, 'Limit of how many tool types to show', VALUE_OPTIONAL),
'offset' => new external_value(PARAM_INT, 'Offset of tool types', VALUE_OPTIONAL),
]);
}
}
@@ -0,0 +1,89 @@
<?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/>.
namespace mod_lti\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
/**
* External function for fetching the count of all tool types and proxies.
*
* @package mod_lti
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_tool_types_and_proxies_count extends external_api {
/**
* Get parameter definition for get_tool_types_and_proxies_count().
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'toolproxyid' => new external_value(PARAM_INT, 'Tool proxy id', VALUE_DEFAULT, 0),
'orphanedonly' => new external_value(PARAM_BOOL, 'Orphaned tool types only', VALUE_DEFAULT, 0),
]
);
}
/**
* Get count of every tool type and tool proxy.
*
* @param int $toolproxyid The tool proxy id
* @param bool $orphanedonly Whether to get orphaned proxies only.
* @return array
*/
public static function execute($toolproxyid, $orphanedonly): array {
$params = self::validate_parameters(self::execute_parameters(),
[
'toolproxyid' => $toolproxyid,
'orphanedonly' => $orphanedonly,
]);
$toolproxyid = $params['toolproxyid'];
$orphanedonly = $params['orphanedonly'];
$context = \context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
return [
'count' => lti_get_lti_types_and_proxies_count($orphanedonly, $toolproxyid),
];
}
/**
* Get return definition for get_tool_types_and_proxies_count.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'count' => new external_value(PARAM_INT, 'Total number of tool types and proxies', VALUE_REQUIRED),
]);
}
}
@@ -0,0 +1,83 @@
<?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/>.
namespace mod_lti\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use mod_lti\local\types_helper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
/**
* External function to toggle showinactivitychooser setting.
*
* @package mod_lti
* @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class toggle_showinactivitychooser extends external_api {
/**
* Get parameter definition.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'tooltypeid' => new external_value(PARAM_INT, 'Tool type ID'),
'courseid' => new external_value(PARAM_INT, 'Course ID'),
'showinactivitychooser' => new external_value(PARAM_BOOL, 'Show in activity chooser'),
]);
}
/**
* Toggles showinactivitychooser setting.
*
* @param int $tooltypeid the id of the course external tool type.
* @param int $courseid the id of the course we are in.
* @param bool $showinactivitychooser Show in activity chooser setting.
* @return bool true or false
*/
public static function execute(int $tooltypeid, int $courseid, bool $showinactivitychooser): bool {
[
'tooltypeid' => $tooltypeid,
'courseid' => $courseid,
'showinactivitychooser' => $showinactivitychooser,
] = self::validate_parameters(self::execute_parameters(), [
'tooltypeid' => $tooltypeid,
'courseid' => $courseid,
'showinactivitychooser' => $showinactivitychooser,
]);
$context = \core\context\course::instance($courseid);
self::validate_context($context);
return types_helper::override_type_showinactivitychooser($tooltypeid, $courseid, $context, $showinactivitychooser);
}
/**
* Get service returns definition.
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, 'Success');
}
}
+60
View File
@@ -0,0 +1,60 @@
<?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/>.
namespace mod_lti;
defined('MOODLE_INTERNAL') || die();
/**
* Helper class for LTI activity.
*
* @package mod_lti
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Get SQL to query DB for LTI tool proxy records.
*
* @param bool $orphanedonly If true, return SQL to get orphaned proxies only.
* @param bool $count If true, return SQL to get the count of the records instead of the records themselves.
* @return string SQL.
*/
public static function get_tool_proxy_sql(bool $orphanedonly = false, bool $count = false): string {
if ($count) {
$select = "SELECT count(*) as type_count";
$sort = "";
} else {
// We only want the fields from lti_tool_proxies table. Must define every column to be compatible with mysqli.
$select = "SELECT ltp.id, ltp.name, ltp.regurl, ltp.state, ltp.guid, ltp.secret, ltp.vendorcode,
ltp.capabilityoffered, ltp.serviceoffered, ltp.toolproxy, ltp.createdby,
ltp.timecreated, ltp.timemodified";
$sort = " ORDER BY ltp.name ASC, ltp.state DESC, ltp.timemodified DESC";
}
$from = " FROM {lti_tool_proxies} ltp";
if ($orphanedonly) {
$join = " LEFT JOIN {lti_types} lt ON ltp.id = lt.toolproxyid";
$where = " WHERE lt.toolproxyid IS null";
} else {
$join = "";
$where = "";
}
return $select . $from . $join . $where . $sort;
}
}
@@ -0,0 +1,138 @@
<?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 files exposes functions for LTI 1.3 Key Management.
*
* @package mod_lti
* @copyright 2020 Claude Vervoort (Cengage)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\local\ltiopenid;
use Firebase\JWT\JWT;
/**
* This class exposes functions for LTI 1.3 Key Management.
*
* @package mod_lti
* @copyright 2020 Claude Vervoort (Cengage)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class jwks_helper {
/**
*
* See https://www.imsglobal.org/spec/security/v1p1#approved-jwt-signing-algorithms.
* @var string[]
*/
private static $ltisupportedalgs = [
'RS256' => 'RSA',
'RS384' => 'RSA',
'RS512' => 'RSA',
'ES256' => 'EC',
'ES384' => 'EC',
'ES512' => 'EC'
];
/**
* Returns the private key to use to sign outgoing JWT.
*
* @return array keys are kid and key in PEM format.
*/
public static function get_private_key() {
$privatekey = get_config('mod_lti', 'privatekey');
$kid = get_config('mod_lti', 'kid');
return [
"key" => $privatekey,
"kid" => $kid
];
}
/**
* Returns the JWK Key Set for this site.
* @return array keyset exposting the site public key.
*/
public static function get_jwks() {
$jwks = array('keys' => array());
$privatekey = self::get_private_key();
$res = openssl_pkey_get_private($privatekey['key']);
$details = openssl_pkey_get_details($res);
// Avoid passing null values to base64_encode.
if (!isset($details['rsa']['e']) || !isset($details['rsa']['n'])) {
throw new \moodle_exception('Error: essential openssl keys not set');
}
$jwk = array();
$jwk['kty'] = 'RSA';
$jwk['alg'] = 'RS256';
$jwk['kid'] = $privatekey['kid'];
$jwk['e'] = rtrim(strtr(base64_encode($details['rsa']['e']), '+/', '-_'), '=');
$jwk['n'] = rtrim(strtr(base64_encode($details['rsa']['n']), '+/', '-_'), '=');
$jwk['use'] = 'sig';
$jwks['keys'][] = $jwk;
return $jwks;
}
/**
* Take an array of JWKS keys and infer the 'alg' property for a single key, if missing, based on an input JWT.
*
* This only sets the 'alg' property for a single key when all the following conditions are met:
* - The key's 'kid' matches the 'kid' provided in the JWT's header.
* - The key's 'alg' is missing.
* - The JWT's header 'alg' matches the algorithm family of the key (the key's kty).
* - The JWT's header 'alg' matches one of the approved LTI asymmetric algorithms.
*
* Keys not matching the above are left unchanged.
*
* @param array $jwks the keyset array.
* @param string $jwt the JWT string.
* @return array the fixed keyset array.
*/
public static function fix_jwks_alg(array $jwks, string $jwt): array {
$jwtparts = explode('.', $jwt);
$jwtheader = json_decode(JWT::urlsafeB64Decode($jwtparts[0]), true);
if (!isset($jwtheader['kid'])) {
throw new \moodle_exception('Error: kid must be provided in JWT header.');
}
foreach ($jwks['keys'] as $index => $key) {
// Only fix the key being referred to in the JWT.
if ($jwtheader['kid'] != $key['kid']) {
continue;
}
// Only fix the key if the alg is missing.
if (!empty($key['alg'])) {
continue;
}
// The header alg must match the key type (family) specified in the JWK's kty.
if (!isset(static::$ltisupportedalgs[$jwtheader['alg']]) ||
static::$ltisupportedalgs[$jwtheader['alg']] != $key['kty']) {
throw new \moodle_exception('Error: Alg specified in the JWT header is incompatible with the JWK key type');
}
$jwks['keys'][$index]['alg'] = $jwtheader['alg'];
}
return $jwks;
}
}
@@ -0,0 +1,32 @@
<?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 library exposes functions for LTI Dynamic Registration.
*
* @package mod_lti
* @copyright 2020 Claude Vervoort (Cengage), Carlos Costa, Adrian Hutchinson (Macgraw Hill)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\local\ltiopenid;
/**
* Exception when transforming the registration to LTI config.
*
* Code is the HTTP Error code.
*/
class registration_exception extends \Exception {
}
@@ -0,0 +1,449 @@
<?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/>.
/**
* A Helper for LTI Dynamic Registration.
*
* @package mod_lti
* @copyright 2020 Claude Vervoort (Cengage), Carlos Costa, Adrian Hutchinson (Macgraw Hill)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\local\ltiopenid;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use stdClass;
/**
* This class exposes functions for LTI Dynamic Registration.
*
* @package mod_lti
* @copyright 2020 Claude Vervoort (Cengage), Carlos Costa, Adrian Hutchinson (Macgraw Hill)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class registration_helper {
/** score scope */
const SCOPE_SCORE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score';
/** result scope */
const SCOPE_RESULT = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly';
/** lineitem read-only scope */
const SCOPE_LINEITEM_RO = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly';
/** lineitem full access scope */
const SCOPE_LINEITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem';
/** Names and Roles (membership) scope */
const SCOPE_NRPS = 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
/** Tool Settings scope */
const SCOPE_TOOL_SETTING = 'https://purl.imsglobal.org/spec/lti-ts/scope/toolsetting';
/** Indicates the token is to create a new registration */
const REG_TOKEN_OP_NEW_REG = 'reg';
/** Indicates the token is to update an existing registration */
const REG_TOKEN_OP_UPDATE_REG = 'reg-update';
/**
* Get an instance of this helper
*
* @return object
*/
public static function get() {
return new registration_helper();
}
/**
* Function used to validate parameters.
*
* This function is needed because the payload contains nested
* objects, and optional_param() does not support arrays of arrays.
*
* @param array $payload that may contain the parameter key
* @param string $key the key of the value to be looked for in the payload
* @param bool $required if required, not finding a value will raise a registration_exception
*
* @return mixed
*/
private function get_parameter(array $payload, string $key, bool $required) {
if (!isset($payload[$key]) || empty($payload[$key])) {
if ($required) {
throw new registration_exception('missing required attribute '.$key, 400);
}
return null;
}
$parameter = $payload[$key];
// Cleans parameters to avoid XSS and other issues.
if (is_array($parameter)) {
return clean_param_array($parameter, PARAM_TEXT, true);
}
return clean_param($parameter, PARAM_TEXT);
}
/**
* Transforms an LTI 1.3 Registration to a Moodle LTI Config.
*
* @param array $registrationpayload the registration data received from the tool.
* @param string $clientid the clientid to be issued for that tool.
*
* @return object the Moodle LTI config.
*/
public function registration_to_config(array $registrationpayload, string $clientid): object {
$responsetypes = $this->get_parameter($registrationpayload, 'response_types', true);
$initiateloginuri = $this->get_parameter($registrationpayload, 'initiate_login_uri', true);
$redirecturis = $this->get_parameter($registrationpayload, 'redirect_uris', true);
$clientname = $this->get_parameter($registrationpayload, 'client_name', true);
$jwksuri = $this->get_parameter($registrationpayload, 'jwks_uri', true);
$tokenendpointauthmethod = $this->get_parameter($registrationpayload, 'token_endpoint_auth_method', true);
$applicationtype = $this->get_parameter($registrationpayload, 'application_type', false);
$logouri = $this->get_parameter($registrationpayload, 'logo_uri', false);
$ltitoolconfiguration = $this->get_parameter($registrationpayload,
'https://purl.imsglobal.org/spec/lti-tool-configuration', true);
$domain = $this->get_parameter($ltitoolconfiguration, 'domain', false);
$targetlinkuri = $this->get_parameter($ltitoolconfiguration, 'target_link_uri', false);
$customparameters = $this->get_parameter($ltitoolconfiguration, 'custom_parameters', false);
$scopes = explode(" ", $this->get_parameter($registrationpayload, 'scope', false) ?? '');
$claims = $this->get_parameter($ltitoolconfiguration, 'claims', false);
$messages = $ltitoolconfiguration['messages'] ?? [];
$description = $this->get_parameter($ltitoolconfiguration, 'description', false);
// Validate domain and target link.
if (empty($domain)) {
throw new registration_exception('missing_domain', 400);
}
$targetlinkuri = $targetlinkuri ?: 'https://'.$domain;
// Stripping www as this is ignored for domain matching.
$domain = lti_get_domain_from_url($domain);
if ($domain !== lti_get_domain_from_url($targetlinkuri)) {
throw new registration_exception('domain_targetlinkuri_mismatch', 400);
}
// Validate response type.
// According to specification, for this scenario, id_token must be explicitly set.
if (!in_array('id_token', $responsetypes)) {
throw new registration_exception('invalid_response_types', 400);
}
// According to specification, this parameter needs to be an array.
if (!is_array($redirecturis)) {
throw new registration_exception('invalid_redirect_uris', 400);
}
// According to specification, for this scenario private_key_jwt must be explicitly set.
if ($tokenendpointauthmethod !== 'private_key_jwt') {
throw new registration_exception('invalid_token_endpoint_auth_method', 400);
}
if (!empty($applicationtype) && $applicationtype !== 'web') {
throw new registration_exception('invalid_application_type', 400);
}
$config = new stdClass();
$config->lti_clientid = $clientid;
$config->lti_toolurl = $targetlinkuri;
$config->lti_tooldomain = $domain;
$config->lti_typename = $clientname;
$config->lti_description = $description;
$config->lti_ltiversion = LTI_VERSION_1P3;
$config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
$config->lti_icon = $logouri;
$config->lti_coursevisible = LTI_COURSEVISIBLE_PRECONFIGURED;
$config->lti_contentitem = 0;
// Sets Content Item.
if (!empty($messages)) {
$messagesresponse = [];
foreach ($messages as $value) {
if ($value['type'] === 'LtiDeepLinkingRequest') {
$config->lti_contentitem = 1;
$config->lti_toolurl_ContentItemSelectionRequest = $value['target_link_uri'] ?? '';
array_push($messagesresponse, $value);
}
}
}
$config->lti_keytype = 'JWK_KEYSET';
$config->lti_publickeyset = $jwksuri;
$config->lti_initiatelogin = $initiateloginuri;
$config->lti_redirectionuris = implode(PHP_EOL, $redirecturis);
$config->lti_customparameters = '';
// Sets custom parameters.
if (isset($customparameters)) {
$paramssarray = [];
foreach ($customparameters as $key => $value) {
array_push($paramssarray, $key . '=' . $value);
}
$config->lti_customparameters = implode(PHP_EOL, $paramssarray);
}
// Sets launch container.
$config->lti_launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
// Sets Service info based on scopes.
$config->lti_acceptgrades = LTI_SETTING_NEVER;
$config->ltiservice_gradesynchronization = 0;
$config->ltiservice_memberships = 0;
$config->ltiservice_toolsettings = 0;
if (isset($scopes)) {
// Sets Assignment and Grade Services info.
if (in_array(self::SCOPE_SCORE, $scopes)) {
$config->lti_acceptgrades = LTI_SETTING_DELEGATE;
$config->ltiservice_gradesynchronization = 1;
}
if (in_array(self::SCOPE_RESULT, $scopes)) {
$config->lti_acceptgrades = LTI_SETTING_DELEGATE;
$config->ltiservice_gradesynchronization = 1;
}
if (in_array(self::SCOPE_LINEITEM_RO, $scopes)) {
$config->lti_acceptgrades = LTI_SETTING_DELEGATE;
$config->ltiservice_gradesynchronization = 1;
}
if (in_array(self::SCOPE_LINEITEM, $scopes)) {
$config->lti_acceptgrades = LTI_SETTING_DELEGATE;
$config->ltiservice_gradesynchronization = 2;
}
// Sets Names and Role Provisioning info.
if (in_array(self::SCOPE_NRPS, $scopes)) {
$config->ltiservice_memberships = 1;
}
// Sets Tool Settings info.
if (in_array(self::SCOPE_TOOL_SETTING, $scopes)) {
$config->ltiservice_toolsettings = 1;
}
}
// Sets privacy settings.
$config->lti_sendname = LTI_SETTING_NEVER;
$config->lti_sendemailaddr = LTI_SETTING_NEVER;
if (isset($claims)) {
// Sets name privacy settings.
if (in_array('name', $claims)) {
$config->lti_sendname = LTI_SETTING_ALWAYS;
}
if (in_array('given_name', $claims)) {
$config->lti_sendname = LTI_SETTING_ALWAYS;
}
if (in_array('family_name', $claims)) {
$config->lti_sendname = LTI_SETTING_ALWAYS;
}
// Sets email privacy settings.
if (in_array('email', $claims)) {
$config->lti_sendemailaddr = LTI_SETTING_ALWAYS;
}
}
return $config;
}
/**
* Adds to the config the LTI 1.1 key and sign it with the 1.1 secret.
*
* @param array $lticonfig reference to lticonfig to which to add the 1.1 OAuth info.
* @param string $key - LTI 1.1 OAuth Key
* @param string $secret - LTI 1.1 OAuth Secret
*
*/
private function add_previous_key_claim(array &$lticonfig, string $key, string $secret) {
if ($key) {
$oauthconsumer = [];
$oauthconsumer['key'] = $key;
$oauthconsumer['nonce'] = random_string(random_int(10, 20));
$oauthconsumer['sign'] = hash('sha256', $key.$secret.$oauthconsumer['nonce']);
$lticonfig['oauth_consumer'] = $oauthconsumer;
}
}
/**
* Transforms a moodle LTI 1.3 Config to an OAuth/LTI Client Registration.
*
* @param object $config Moodle LTI Config.
* @param int $typeid which is the LTI deployment id.
* @param object $type tool instance in case the tool already exists.
*
* @return array the Client Registration as an associative array.
*/
public function config_to_registration(object $config, int $typeid, object $type = null): array {
$configarray = [];
foreach ((array)$config as $k => $v) {
if (substr($k, 0, 4) == 'lti_') {
$k = substr($k, 4);
}
$configarray[$k] = $v;
}
$config = (object) $configarray;
$registrationresponse = [];
$lticonfigurationresponse = [];
$ltiversion = $type ? $type->ltiversion : $config->ltiversion;
$lticonfigurationresponse['version'] = $ltiversion;
if ($ltiversion === LTI_VERSION_1P3) {
$registrationresponse['client_id'] = $type ? $type->clientid : $config->clientid;
$registrationresponse['response_types'] = ['id_token'];
$registrationresponse['jwks_uri'] = $config->publickeyset;
$registrationresponse['initiate_login_uri'] = $config->initiatelogin;
$registrationresponse['grant_types'] = ['client_credentials', 'implicit'];
$registrationresponse['redirect_uris'] = explode(PHP_EOL, $config->redirectionuris);
$registrationresponse['application_type'] = 'web';
$registrationresponse['token_endpoint_auth_method'] = 'private_key_jwt';
} else if ($ltiversion === LTI_VERSION_1 && $type) {
$this->add_previous_key_claim($lticonfigurationresponse, $config->resourcekey, $config->password);
} else if ($ltiversion === LTI_VERSION_2 && $type) {
$toolproxy = $this->get_tool_proxy($type->toolproxyid);
$this->add_previous_key_claim($lticonfigurationresponse, $toolproxy['guid'], $toolproxy['secret']);
}
$registrationresponse['client_name'] = $type ? $type->name : $config->typename;
$registrationresponse['logo_uri'] = $type ? ($type->secureicon ?? $type->icon ?? '') : $config->icon ?? '';
$lticonfigurationresponse['deployment_id'] = strval($typeid);
$lticonfigurationresponse['target_link_uri'] = $type ? $type->baseurl : $config->toolurl ?? '';
$lticonfigurationresponse['domain'] = $type ? $type->tooldomain : $config->tooldomain ?? '';
$lticonfigurationresponse['description'] = $type ? $type->description ?? '' : $config->description ?? '';
if ($config->contentitem ?? 0 == 1) {
$contentitemmessage = [];
$contentitemmessage['type'] = 'LtiDeepLinkingRequest';
if (isset($config->toolurl_ContentItemSelectionRequest)) {
$contentitemmessage['target_link_uri'] = $config->toolurl_ContentItemSelectionRequest;
}
$lticonfigurationresponse['messages'] = [$contentitemmessage];
}
if (isset($config->customparameters) && !empty($config->customparameters)) {
$params = [];
foreach (explode(PHP_EOL, $config->customparameters) as $param) {
$split = explode('=', $param);
$params[$split[0]] = $split[1];
}
$lticonfigurationresponse['custom_parameters'] = $params;
}
$scopesresponse = [];
if ($config->ltiservice_gradesynchronization ?? 0 > 0) {
$scopesresponse[] = self::SCOPE_SCORE;
$scopesresponse[] = self::SCOPE_RESULT;
$scopesresponse[] = self::SCOPE_LINEITEM_RO;
}
if ($config->ltiservice_gradesynchronization ?? 0 == 2) {
$scopesresponse[] = self::SCOPE_LINEITEM;
}
if ($config->ltiservice_memberships ?? 0 == 1) {
$scopesresponse[] = self::SCOPE_NRPS;
}
if ($config->ltiservice_toolsettings ?? 0 == 1) {
$scopesresponse[] = self::SCOPE_TOOL_SETTING;
}
$registrationresponse['scope'] = implode(' ', $scopesresponse);
$claimsresponse = ['sub', 'iss'];
if ($config->sendname ?? '' == LTI_SETTING_ALWAYS) {
$claimsresponse[] = 'name';
$claimsresponse[] = 'family_name';
$claimsresponse[] = 'given_name';
}
if ($config->sendemailaddr ?? '' == LTI_SETTING_ALWAYS) {
$claimsresponse[] = 'email';
}
$lticonfigurationresponse['claims'] = $claimsresponse;
$registrationresponse['https://purl.imsglobal.org/spec/lti-tool-configuration'] = $lticonfigurationresponse;
return $registrationresponse;
}
/**
* Validates the registration token is properly signed and not used yet.
* Return the client id to use for this registration.
*
* @param string $registrationtokenjwt registration token
*
* @return array with 2 keys: clientid for the registration, type but only if it's an update
*/
public function validate_registration_token(string $registrationtokenjwt): array {
global $DB;
// JWK::parseKeySet uses RS256 algorithm by default.
$keys = JWK::parseKeySet(jwks_helper::get_jwks());
$registrationtoken = JWT::decode($registrationtokenjwt, $keys);
$response = [];
// Get clientid from registrationtoken.
$clientid = $registrationtoken->sub;
if ($registrationtoken->scope == self::REG_TOKEN_OP_NEW_REG) {
// Checks if clientid is already registered.
if (!empty($DB->get_record('lti_types', array('clientid' => $clientid)))) {
throw new registration_exception("token_already_used", 401);
}
$response['clientid'] = $clientid;
} else if ($registrationtoken->scope == self::REG_TOKEN_OP_UPDATE_REG) {
$tool = lti_get_type($registrationtoken->sub);
if (!$tool) {
throw new registration_exception("Unknown client", 400);
}
$response['clientid'] = $tool->clientid ?? $this->new_clientid();
$response['type'] = $tool;
} else {
throw new registration_exception("Incorrect scope", 403);
}
return $response;
}
/**
* Initializes an array with the scopes for services supported by the LTI module
*
* @return array List of scopes
*/
public function lti_get_service_scopes() {
$services = lti_get_services();
$scopes = array();
foreach ($services as $service) {
$servicescopes = $service->get_scopes();
if (!empty($servicescopes)) {
$scopes = array_merge($scopes, $servicescopes);
}
}
return $scopes;
}
/**
* Generates a new client id string.
*
* @return string generated client id
*/
public function new_clientid(): string {
return random_string(15);
}
/**
* Base64 encoded signature for LTI 1.1 migration.
* @param string $key LTI 1.1 key
* @param string $salt Salt value
* @param string $secret LTI 1.1 secret
*
* @return string base64encoded hash
*/
public function sign(string $key, string $salt, string $secret): string {
return base64_encode(hash_hmac('sha-256', $key.$salt, $secret, true));
}
/**
* Returns a tool proxy
*
* @param int $proxyid
*
* @return mixed Tool Proxy details
*/
public function get_tool_proxy(int $proxyid): array {
return lti_get_tool_proxy($proxyid);
}
}
@@ -0,0 +1,389 @@
<?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 contains an abstract definition of an LTI resource
*
* @package mod_lti
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @author Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\local\ltiservice;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
/**
* The mod_lti\local\ltiservice\resource_base class.
*
* @package mod_lti
* @since Moodle 2.8
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class resource_base {
/** HTTP Post method */
const HTTP_POST = 'POST';
/** HTTP Get method */
const HTTP_GET = 'GET';
/** HTTP Put method */
const HTTP_PUT = 'PUT';
/** HTTP Delete method */
const HTTP_DELETE = 'DELETE';
/** @var service_base Service associated with this resource. */
private $service;
/** @var string Type for this resource. */
protected $type;
/** @var string ID for this resource. */
protected $id;
/** @var string Template for this resource. */
protected $template;
/** @var array Custom parameter substitution variables associated with this resource. */
protected $variables;
/** @var array Media types supported by this resource. */
protected $formats;
/** @var array HTTP actions supported by this resource. */
protected $methods;
/** @var array Template variables parsed from the resource template. */
protected $params;
/**
* Class constructor.
*
* @param service_base $service Service instance
*/
public function __construct($service) {
$this->service = $service;
$this->type = 'RestService';
$this->id = null;
$this->template = null;
$this->methods = array();
$this->variables = array();
$this->formats = array();
$this->methods = array();
$this->params = null;
}
/**
* Get the resource ID.
*
* @return string
*/
public function get_id() {
return $this->id;
}
/**
* Get the resource template.
*
* @return string
*/
public function get_template() {
return $this->template;
}
/**
* Get the resource path.
*
* @return string
*/
public function get_path() {
return $this->get_template();
}
/**
* Get the resource type.
*
* @return string
*/
public function get_type() {
return $this->type;
}
/**
* Get the resource's service.
*
* @return service_base
*/
public function get_service() {
return $this->service;
}
/**
* Get the resource methods.
*
* @return array
*/
public function get_methods() {
return $this->methods;
}
/**
* Get the resource media types.
*
* @return array
*/
public function get_formats() {
return $this->formats;
}
/**
* Get the resource template variables.
*
* @return array
*/
public function get_variables() {
return $this->variables;
}
/**
* Get the resource fully qualified endpoint.
*
* @return string
*/
public function get_endpoint() {
$this->parse_template();
$template = preg_replace('/[\(\)]/', '', $this->get_template());
$url = $this->get_service()->get_service_path() . $template;
foreach ($this->params as $key => $value) {
$url = str_replace('{' . $key . '}', $value, $url);
}
$toolproxy = $this->get_service()->get_tool_proxy();
if (!empty($toolproxy)) {
$url = str_replace('{config_type}', 'toolproxy', $url);
$url = str_replace('{tool_proxy_id}', $toolproxy->guid, $url);
} else {
$url = str_replace('{config_type}', 'tool', $url);
$url = str_replace('{tool_proxy_id}', $this->get_service()->get_type()->id, $url);
}
return $url;
}
/**
* Execute the request for this resource.
*
* @param response $response Response object for this request.
*/
abstract public function execute($response);
/**
* Check to make sure the request is valid.
*
* @param int $typeid The typeid we want to use
* @param string $body Body of HTTP request message
* @param string[] $scopes Array of scope(s) required for incoming request
*
* @return boolean
*/
public function check_tool($typeid, $body = null, $scopes = null) {
$ok = $this->get_service()->check_tool($typeid, $body, $scopes);
if ($ok) {
if ($this->get_service()->get_tool_proxy()) {
$toolproxyjson = $this->get_service()->get_tool_proxy()->toolproxy;
}
if (!empty($toolproxyjson)) {
// Check tool proxy to ensure service being requested is included.
$toolproxy = json_decode($toolproxyjson);
if (!empty($toolproxy) && isset($toolproxy->security_contract->tool_service)) {
$contexts = lti_get_contexts($toolproxy);
$tpservices = $toolproxy->security_contract->tool_service;
foreach ($tpservices as $service) {
$fqid = lti_get_fqid($contexts, $service->service);
$id = explode('#', $fqid, 2);
if ($this->get_id() === $id[1]) {
$ok = true;
break;
}
}
}
if (!$ok) {
debugging('Requested service not permitted: ' . $this->get_id(), DEBUG_DEVELOPER);
}
} else {
// Check that the scope required for the service request is included in those granted for the
// access token being used.
$permittedscopes = $this->get_service()->get_permitted_scopes();
$ok = is_null($permittedscopes) || empty($scopes) || !empty(array_intersect($permittedscopes, $scopes));
}
}
return $ok;
}
/**
* Check to make sure the request is valid.
*
* @param string $toolproxyguid Consumer key
* @param string $body Body of HTTP request message
*
* @return boolean
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
* @see resource_base::check_tool()
*/
public function check_tool_proxy($toolproxyguid, $body = null) {
debugging('check_tool_proxy() is deprecated to allow LTI 1 connections to support services. ' .
'Please use resource_base::check_tool() instead.', DEBUG_DEVELOPER);
$ok = false;
if ($this->get_service()->check_tool_proxy($toolproxyguid, $body)) {
$toolproxyjson = $this->get_service()->get_tool_proxy()->toolproxy;
if (empty($toolproxyjson)) {
$ok = true;
} else {
$toolproxy = json_decode($toolproxyjson);
if (!empty($toolproxy) && isset($toolproxy->security_contract->tool_service)) {
$contexts = lti_get_contexts($toolproxy);
$tpservices = $toolproxy->security_contract->tool_service;
foreach ($tpservices as $service) {
$fqid = lti_get_fqid($contexts, $service->service);
$id = explode('#', $fqid, 2);
if ($this->get_id() === $id[1]) {
$ok = true;
break;
}
}
}
if (!$ok) {
debugging('Requested service not included in tool proxy: ' . $this->get_id());
}
}
}
return $ok;
}
/**
* Check to make sure the request is valid.
*
* @param int $typeid The typeid we want to use
* @param int $contextid The course we are at
* @param string $permissionrequested The permission to be checked
* @param string $body Body of HTTP request message
*
* @return boolean
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
* @see resource_base::check_tool()
*/
public function check_type($typeid, $contextid, $permissionrequested, $body = null) {
debugging('check_type() is deprecated to allow LTI 1 connections to support services. ' .
'Please use resource_base::check_tool() instead.', DEBUG_DEVELOPER);
$ok = false;
if ($this->get_service()->check_type($typeid, $contextid, $body)) {
$neededpermissions = $this->get_permissions($typeid);
foreach ($neededpermissions as $permission) {
if ($permission == $permissionrequested) {
$ok = true;
break;
}
}
if (!$ok) {
debugging('Requested service ' . $permissionrequested . ' not included in tool type: ' . $typeid,
DEBUG_DEVELOPER);
}
}
return $ok;
}
/**
* get permissions from the config of the tool for that resource
*
* @param int $ltitype Type of LTI
* @return array with the permissions related to this resource by the $ltitype or empty if none.
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
* @see resource_base::check_tool()
*/
public function get_permissions($ltitype) {
debugging('get_permissions() is deprecated to allow LTI 1 connections to support services. ' .
'Please use resource_base::check_tool() instead.', DEBUG_DEVELOPER);
return array();
}
/**
* Parse a value for custom parameter substitution variables.
*
* @param string $value String to be parsed
*
* @return string
*/
public function parse_value($value) {
return $value;
}
/**
* Parse the template for variables.
*
* @return array
*/
protected function parse_template() {
if (empty($this->params)) {
$this->params = array();
if (!empty($_SERVER['PATH_INFO'])) {
$path = explode('/', $_SERVER['PATH_INFO']);
$template = preg_replace('/\([0-9a-zA-Z_\-,\/]+\)/', '', $this->get_template());
$parts = explode('/', $template);
for ($i = 0; $i < count($parts); $i++) {
if ((substr($parts[$i], 0, 1) == '{') && (substr($parts[$i], -1) == '}')) {
$value = '';
if ($i < count($path)) {
$value = $path[$i];
}
$this->params[substr($parts[$i], 1, -1)] = $value;
}
}
}
}
return $this->params;
}
}
@@ -0,0 +1,263 @@
<?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 contains an abstract definition of an LTI service
*
* @package mod_lti
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @author Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\local\ltiservice;
defined('MOODLE_INTERNAL') || die;
/**
* The mod_lti\local\ltiservice\response class.
*
* @package mod_lti
* @since Moodle 2.8
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class response {
/** @var int HTTP response code. */
private $code;
/** @var string HTTP response reason. */
private $reason;
/** @var string HTTP request method. */
private $requestmethod;
/** @var string HTTP request accept header. */
private $accept;
/** @var string HTTP response content type. */
private $contenttype;
/** @var string HTTP request body. */
private $data;
/** @var string HTTP response body. */
private $body;
/** @var array HTTP response codes. */
private $responsecodes;
/** @var array HTTP additional headers. */
private $additionalheaders;
/**
* Class constructor.
*/
public function __construct() {
$this->code = 200;
$this->reason = '';
$this->requestmethod = $_SERVER['REQUEST_METHOD'];
$this->accept = '';
$this->contenttype = '';
$this->data = '';
$this->body = '';
$this->responsecodes = array(
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
300 => 'Multiple Choices',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
415 => 'Unsupported Media Type',
500 => 'Internal Server Error',
501 => 'Not Implemented'
);
$this->additionalheaders = array();
}
/**
* Get the response code.
*
* @return int
*/
public function get_code() {
return $this->code;
}
/**
* Set the response code.
*
* @param int $code Response code
*/
public function set_code($code) {
$this->code = $code;
$this->reason = '';
}
/**
* Get the response reason.
*
* @return string
*/
public function get_reason() {
$code = $this->code;
if (($code < 200) || ($code >= 600)) {
$code = 500; // Status code must be between 200 and 599.
}
if (empty($this->reason) && array_key_exists($code, $this->responsecodes)) {
$this->reason = $this->responsecodes[$code];
}
// Use generic reason for this category (based on first digit) if a specific reason is not defined.
if (empty($this->reason)) {
$this->reason = $this->responsecodes[intval($code / 100) * 100];
}
return $this->reason;
}
/**
* Set the response reason.
*
* @param string $reason Reason
*/
public function set_reason($reason) {
$this->reason = $reason;
}
/**
* Get the request method.
*
* @return string
*/
public function get_request_method() {
return $this->requestmethod;
}
/**
* Get the request accept header.
*
* @return string
*/
public function get_accept() {
return $this->accept;
}
/**
* Set the request accept header.
*
* @param string $accept Accept header value
*/
public function set_accept($accept) {
$this->accept = $accept;
}
/**
* Get the response content type.
*
* @return string
*/
public function get_content_type() {
return $this->contenttype;
}
/**
* Set the response content type.
*
* @param string $contenttype Content type
*/
public function set_content_type($contenttype) {
$this->contenttype = $contenttype;
}
/**
* Get the request body.
*
* @return string
*/
public function get_request_data() {
return $this->data;
}
/**
* Set the response body.
*
* @param string $data Body data
*/
public function set_request_data($data) {
$this->data = $data;
}
/**
* Get the response body.
*
* @return string
*/
public function get_body() {
return $this->body;
}
/**
* Set the response body.
*
* @param string $body Body data
*/
public function set_body($body) {
$this->body = $body;
}
/**
* Add an additional header.
*
* @param string $header The new header
*/
public function add_additional_header($header) {
array_push($this->additionalheaders, $header);
}
/**
* Send the response.
*/
public function send() {
header("HTTP/1.0 {$this->code} {$this->get_reason()}");
foreach ($this->additionalheaders as $header) {
header($header);
}
if ((($this->code >= 200) && ($this->code < 300)) || !empty($this->body)) {
if (!empty($this->contenttype)) {
header("Content-Type: {$this->contenttype}; charset=utf-8");
}
if (!empty($this->body)) {
echo $this->body;
}
} else if ($this->code >= 400) {
header("Content-Type: application/json; charset=utf-8");
$body = new \stdClass();
$body->status = $this->code;
$body->reason = $this->get_reason();
$body->request = new \stdClass();
$body->request->method = $_SERVER['REQUEST_METHOD'];
$body->request->url = $_SERVER['REQUEST_URI'];
if (isset($_SERVER['HTTP_ACCEPT'])) {
$body->request->accept = $_SERVER['HTTP_ACCEPT'];
}
if (isset($_SERVER['CONTENT_TYPE'])) {
$body->request->contentType = explode(';', $_SERVER['CONTENT_TYPE'], 2)[0];
}
echo json_encode($body, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
}
}
@@ -0,0 +1,548 @@
<?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 contains an abstract definition of an LTI service
*
* @package mod_lti
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @author Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\local\ltiservice;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
require_once($CFG->dirroot . '/mod/lti/OAuthBody.php');
// TODO: Switch to core oauthlib once implemented - MDL-30149.
use moodle\mod\lti as lti;
use stdClass;
/**
* The mod_lti\local\ltiservice\service_base class.
*
* @package mod_lti
* @since Moodle 2.8
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class service_base {
/** Label representing an LTI 2 message type */
const LTI_VERSION2P0 = 'LTI-2p0';
/** Service enabled */
const SERVICE_ENABLED = 1;
/** @var string ID for the service. */
protected $id;
/** @var string Human readable name for the service. */
protected $name;
/** @var boolean <code>true</code> if requests for this service do not need to be signed. */
protected $unsigned;
/** @var stdClass Tool proxy object for the current service request. */
private $toolproxy;
/** @var stdClass LTI type object for the current service request. */
private $type;
/** @var array LTI type config array for the current service request. */
private $typeconfig;
/** @var array Instances of the resources associated with this service. */
protected $resources;
/**
* Class constructor.
*/
public function __construct() {
$this->id = null;
$this->name = null;
$this->unsigned = false;
$this->toolproxy = null;
$this->type = null;
$this->typeconfig = null;
$this->resources = null;
}
/**
* Get the service ID.
*
* @return string
*/
public function get_id() {
return $this->id;
}
/**
* Get the service compoent ID.
*
* @return string
*/
public function get_component_id() {
return 'ltiservice_' . $this->id;
}
/**
* Get the service name.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Get whether the service requests need to be signed.
*
* @return boolean
*/
public function is_unsigned() {
return $this->unsigned;
}
/**
* Get the tool proxy object.
*
* @return stdClass
*/
public function get_tool_proxy() {
return $this->toolproxy;
}
/**
* Set the tool proxy object.
*
* @param object $toolproxy The tool proxy for this service request
*
* @var stdClass
*/
public function set_tool_proxy($toolproxy) {
$this->toolproxy = $toolproxy;
}
/**
* Get the type object.
*
* @return stdClass
*/
public function get_type() {
return $this->type;
}
/**
* Set the LTI type object.
*
* @param object $type The LTI type for this service request
*
* @var stdClass
*/
public function set_type($type) {
$this->type = $type;
}
/**
* Get the type config array.
*
* @return array|null
*/
public function get_typeconfig() {
return $this->typeconfig;
}
/**
* Set the LTI type config object.
*
* @param array $typeconfig The LTI type config for this service request
*
* @var array
*/
public function set_typeconfig($typeconfig) {
$this->typeconfig = $typeconfig;
}
/**
* Get the resources for this service.
*
* @return resource_base[]
*/
abstract public function get_resources();
/**
* Get the scope(s) permitted for this service in the context of a particular tool type.
*
* A null value indicates that no scopes are required to access the service.
*
* @return array|null
*/
public function get_permitted_scopes() {
return null;
}
/**
* Get the scope(s) permitted for this service.
*
* A null value indicates that no scopes are required to access the service.
*
* @return array|null
*/
public function get_scopes() {
return null;
}
/**
* Returns the configuration options for this service.
*
* @param \MoodleQuickForm $mform Moodle quickform object definition
*/
public function get_configuration_options(&$mform) {
}
/**
* Called when a new LTI Instance is added.
*
* @param object $lti LTI Instance.
*/
public function instance_added(object $lti): void {
}
/**
* Called when a new LTI Instance is updated.
*
* @param object $lti LTI Instance.
*/
public function instance_updated(object $lti): void {
}
/**
* Called when the launch data is created, offering a possibility to alter the
* target link URI.
*
* @param string $messagetype message type for this launch
* @param string $targetlinkuri current target link uri
* @param null|string $customstr concatenated list of custom parameters
* @param int $courseid
* @param null|object $lti LTI Instance.
*
* @return array containing the target link URL and the custom params string to use.
*/
public function override_endpoint(string $messagetype, string $targetlinkuri,
?string $customstr, int $courseid, ?object $lti = null): array {
return [$targetlinkuri, $customstr];
}
/**
* Called when a new LTI Instance is deleted.
*
* @param int $id LTI Instance.
*/
public function instance_deleted(int $id): void {
}
/**
* Set the form data when displaying the LTI Instance form.
*
* @param object $defaultvalues Default form values.
*/
public function set_instance_form_values(object $defaultvalues): void {
}
/**
* Return an array with the names of the parameters that the service will be saving in the configuration
*
* @return array Names list of the parameters that the service will be saving in the configuration
* @deprecated since Moodle 3.7 - please do not use this function any more.
*/
public function get_configuration_parameter_names() {
debugging('get_configuration_parameter_names() has been deprecated.', DEBUG_DEVELOPER);
return array();
}
/**
* Default implementation will check for the existence of at least one mod_lti entry for that tool and context.
*
* It may be overridden if other inferences can be done.
*
* Ideally a Site Tool should be explicitly engaged with a course, the check on the presence of a link is a proxy
* to infer a Site Tool engagement until an explicit Site Tool - Course relationship exists.
*
* @param int $typeid The tool lti type id.
* @param int $courseid The course id.
* @return bool returns True if tool is used in context, false otherwise.
*/
public function is_used_in_context($typeid, $courseid) {
global $DB;
$ok = $DB->record_exists('lti', array('course' => $courseid, 'typeid' => $typeid));
return $ok || $DB->record_exists('lti_types', array('course' => $courseid, 'id' => $typeid));
}
/**
* Checks if there is a site tool or a course tool for this site.
*
* @param int $typeid The tool lti type id.
* @param int $courseid The course id.
* @return bool returns True if tool is allowed in context, false otherwise.
*/
public function is_allowed_in_context($typeid, $courseid) {
global $DB;
// Check if it is a Course tool for this course or a Site tool.
$type = $DB->get_record('lti_types', array('id' => $typeid));
return $type && ($type->course == $courseid || $type->course == SITEID);
}
/**
* Return an array of key/values to add to the launch parameters.
*
* @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
* @param string $courseid The course id.
* @param string $userid The user id.
* @param string $typeid The tool lti type id.
* @param string $modlti The id of the lti activity.
*
* The type is passed to check the configuration and not return parameters for services not used.
*
* @return array Key/value pairs to add as launch parameters.
*/
public function get_launch_parameters($messagetype, $courseid, $userid, $typeid, $modlti = null) {
return array();
}
/**
* Return an array of key/claim mapping allowing LTI 1.1 custom parameters
* to be transformed to LTI 1.3 claims.
*
* @return array Key/value pairs of params to claim mapping.
*/
public function get_jwt_claim_mappings(): array {
return [];
}
/**
* Get the path for service requests.
*
* @return string
*/
public static function get_service_path() {
$url = new \moodle_url('/mod/lti/services.php');
return $url->out(false);
}
/**
* Parse a string for custom substitution parameter variables supported by this service's resources.
*
* @param string $value Value to be parsed
*
* @return string
*/
public function parse_value($value) {
if (empty($this->resources)) {
$this->resources = $this->get_resources();
}
if (!empty($this->resources)) {
foreach ($this->resources as $resource) {
$value = $resource->parse_value($value);
}
}
return $value;
}
/**
* Check that the request has been properly signed and is permitted.
*
* @param string $typeid LTI type ID
* @param string $body Request body (null if none)
* @param string[] $scopes Array of required scope(s) for incoming request
*
* @return boolean
*/
public function check_tool($typeid, $body = null, $scopes = null) {
$ok = true;
$toolproxy = null;
$consumerkey = lti\get_oauth_key_from_headers($typeid, $scopes);
if ($consumerkey === false) {
$ok = $this->is_unsigned();
} else {
if (empty($typeid) && is_int($consumerkey)) {
$typeid = $consumerkey;
}
if (!empty($typeid)) {
$this->type = lti_get_type($typeid);
$this->typeconfig = lti_get_type_config($typeid);
$ok = !empty($this->type->id);
if ($ok && !empty($this->type->toolproxyid)) {
$this->toolproxy = lti_get_tool_proxy($this->type->toolproxyid);
}
} else {
$toolproxy = lti_get_tool_proxy_from_guid($consumerkey);
if ($toolproxy !== false) {
$this->toolproxy = $toolproxy;
}
}
}
if ($ok && is_string($consumerkey)) {
if (!empty($this->toolproxy)) {
$key = $this->toolproxy->guid;
$secret = $this->toolproxy->secret;
} else {
$key = $this->typeconfig['resourcekey'];
$secret = $this->typeconfig['password'];
}
if (!$this->is_unsigned() && ($key == $consumerkey)) {
$ok = $this->check_signature($key, $secret, $body);
} else {
$ok = $this->is_unsigned();
}
}
return $ok;
}
/**
* Check that the request has been properly signed.
*
* @param string $toolproxyguid Tool Proxy GUID
* @param string $body Request body (null if none)
*
* @return boolean
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
* @see service_base::check_tool()
*/
public function check_tool_proxy($toolproxyguid, $body = null) {
debugging('check_tool_proxy() is deprecated to allow LTI 1 connections to support services. ' .
'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
$ok = false;
$toolproxy = null;
$consumerkey = lti\get_oauth_key_from_headers();
if (empty($toolproxyguid)) {
$toolproxyguid = $consumerkey;
}
if (!empty($toolproxyguid)) {
$toolproxy = lti_get_tool_proxy_from_guid($toolproxyguid);
if ($toolproxy !== false) {
if (!$this->is_unsigned() && ($toolproxy->guid == $consumerkey)) {
$ok = $this->check_signature($toolproxy->guid, $toolproxy->secret, $body);
} else {
$ok = $this->is_unsigned();
}
}
}
if ($ok) {
$this->toolproxy = $toolproxy;
}
return $ok;
}
/**
* Check that the request has been properly signed.
*
* @param int $typeid The tool id
* @param int $courseid The course we are at
* @param string $body Request body (null if none)
*
* @return bool
* @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
* @see service_base::check_tool()
*/
public function check_type($typeid, $courseid, $body = null) {
debugging('check_type() is deprecated to allow LTI 1 connections to support services. ' .
'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
$ok = false;
$tool = null;
$consumerkey = lti\get_oauth_key_from_headers();
if (empty($typeid)) {
return $ok;
} else if ($this->is_allowed_in_context($typeid, $courseid)) {
$tool = lti_get_type_type_config($typeid);
if ($tool !== false) {
if (!$this->is_unsigned() && ($tool->lti_resourcekey == $consumerkey)) {
$ok = $this->check_signature($tool->lti_resourcekey, $tool->lti_password, $body);
} else {
$ok = $this->is_unsigned();
}
}
}
return $ok;
}
/**
* Check the request signature.
*
* @param string $consumerkey Consumer key
* @param string $secret Shared secret
* @param string $body Request body
*
* @return boolean
*/
private function check_signature($consumerkey, $secret, $body) {
$ok = true;
try {
// TODO: Switch to core oauthlib once implemented - MDL-30149.
lti\handle_oauth_body_post($consumerkey, $secret, $body);
} catch (\Exception $e) {
debugging($e->getMessage() . "\n");
$ok = false;
}
return $ok;
}
}
+142
View File
@@ -0,0 +1,142 @@
<?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/>.
namespace mod_lti\local;
use core\context\course;
/**
* Helper class specifically dealing with LTI types (preconfigured tools).
*
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class types_helper {
/**
* Returns all LTI tool types (preconfigured tools) visible in the given course and for the given user.
*
* This list will contain both site level tools and course-level tools.
*
* @param int $courseid the id of the course.
* @param int $userid the id of the user.
* @param array $coursevisible options for 'coursevisible' field, which will default to
* [LTI_COURSEVISIBLE_PRECONFIGURED, LTI_COURSEVISIBLE_ACTIVITYCHOOSER] if omitted.
* @return \stdClass[] the array of tool type objects.
*/
public static function get_lti_types_by_course(int $courseid, int $userid, array $coursevisible = []): array {
global $DB, $SITE;
if (!has_capability('mod/lti:addpreconfiguredinstance', course::instance($courseid), $userid)) {
return [];
}
if (empty($coursevisible)) {
$coursevisible = [LTI_COURSEVISIBLE_PRECONFIGURED, LTI_COURSEVISIBLE_ACTIVITYCHOOSER];
}
[$coursevisiblesql, $coursevisparams] = $DB->get_in_or_equal($coursevisible, SQL_PARAMS_NAMED, 'coursevisible');
[$coursevisiblesql1, $coursevisparams1] = $DB->get_in_or_equal($coursevisible, SQL_PARAMS_NAMED, 'coursevisible');
[$coursevisibleoverriddensql, $coursevisoverriddenparams] = $DB->get_in_or_equal(
$coursevisible,
SQL_PARAMS_NAMED,
'coursevisibleoverridden');
$coursecond = implode(" OR ", ["t.course = :courseid", "t.course = :siteid"]);
$coursecategory = $DB->get_field('course', 'category', ['id' => $courseid]);
$query = "SELECT *
FROM (SELECT t.*, c.coursevisible as coursevisibleoverridden
FROM {lti_types} t
LEFT JOIN {lti_types_categories} tc ON t.id = tc.typeid
LEFT JOIN {lti_coursevisible} c ON c.typeid = t.id AND c.courseid = $courseid
WHERE (t.coursevisible $coursevisiblesql
OR (c.coursevisible $coursevisiblesql1 AND t.coursevisible NOT IN (:lticoursevisibleno)))
AND ($coursecond)
AND t.state = :active
AND (tc.id IS NULL OR tc.categoryid = :categoryid)) tt
WHERE tt.coursevisibleoverridden IS NULL
OR tt.coursevisibleoverridden $coursevisibleoverriddensql";
return $DB->get_records_sql(
$query,
[
'siteid' => $SITE->id,
'courseid' => $courseid,
'active' => LTI_TOOL_STATE_CONFIGURED,
'categoryid' => $coursecategory,
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER,
'lticoursevisibleno' => LTI_COURSEVISIBLE_NO,
] + $coursevisparams + $coursevisparams1 + $coursevisoverriddenparams
);
}
/**
* Override coursevisible for a given tool on course level.
*
* @param int $tooltypeid Type ID
* @param int $courseid Course ID
* @param \core\context\course $context Course context
* @param bool $showinactivitychooser Show or not show in activity chooser
* @return bool True if the coursevisible was changed, false otherwise.
*/
public static function override_type_showinactivitychooser(int $tooltypeid, int $courseid, \core\context\course $context, bool $showinactivitychooser): bool {
global $DB;
require_capability('mod/lti:addcoursetool', $context);
$ltitype = lti_get_type($tooltypeid);
if ($ltitype && ($ltitype->coursevisible != LTI_COURSEVISIBLE_NO)) {
$coursevisible = $showinactivitychooser ? LTI_COURSEVISIBLE_ACTIVITYCHOOSER : LTI_COURSEVISIBLE_PRECONFIGURED;
$ltitype->coursevisible = $coursevisible;
$config = new \stdClass();
$config->lti_coursevisible = $coursevisible;
if (intval($ltitype->course) != intval(get_site()->id)) {
// It is course tool - just update it.
lti_update_type($ltitype, $config);
} else {
$coursecategory = $DB->get_field('course', 'category', ['id' => $courseid]);
$sql = "SELECT COUNT(*) AS count
FROM {lti_types_categories} tc
WHERE tc.typeid = :typeid";
$restrictedtool = $DB->count_records_sql($sql, ['typeid' => $tooltypeid]);
if ($restrictedtool) {
$record = $DB->get_record('lti_types_categories', ['typeid' => $tooltypeid, 'categoryid' => $coursecategory]);
if (!$record) {
throw new \moodle_exception('You are not allowed to change this setting for this tool.');
}
}
// This is site tool, but we would like to have course level setting for it.
$lticoursevisible = $DB->get_record('lti_coursevisible', ['typeid' => $tooltypeid, 'courseid' => $courseid]);
if (!$lticoursevisible) {
$lticoursevisible = new \stdClass();
$lticoursevisible->typeid = $tooltypeid;
$lticoursevisible->courseid = $courseid;
$lticoursevisible->coursevisible = $coursevisible;
$DB->insert_record('lti_coursevisible', $lticoursevisible);
} else {
$lticoursevisible->coursevisible = $coursevisible;
$DB->update_record('lti_coursevisible', $lticoursevisible);
}
}
return true;
}
return false;
}
}
@@ -0,0 +1,77 @@
<?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/>.
namespace mod_lti\output;
use core_reportbuilder\system_report_factory;
use mod_lti\reportbuilder\local\systemreports\course_external_tools_list;
/**
* The course tools page renderable, containing a page header renderable and a course tools system report.
*
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_tools_page implements \renderable {
/** @var course_external_tools_list the course tools system report instance. */
protected course_external_tools_list $coursetoolsreport;
/** @var course_tools_page_header the page header renderable instance. */
protected course_tools_page_header $coursetoolspageheader;
/**
* Renderable constructor.
*
* @param int $courseid the id of the course.
*/
public function __construct(int $courseid) {
global $DB;
$context = \context_course::instance($courseid);
// Page intro, zero state and 'add new' button.
$canadd = has_capability('mod/lti:addcoursetool', $context);
$sql = 'SELECT COUNT(1)
FROM {lti_types} tt
WHERE tt.course IN(:siteid, :courseid)
AND tt.coursevisible NOT IN(:coursevisible)';
$toolcount = $DB->count_records_sql($sql, ['siteid' => get_site()->id, 'courseid' => $courseid, 'coursevisible' => 0]);
$this->coursetoolspageheader = new course_tools_page_header($courseid, $toolcount, $canadd);
// Course tools report itself.
$this->coursetoolsreport = system_report_factory::create(course_external_tools_list::class, $context);
}
/**
* Get the course tools page header renderable.
*
* @return course_tools_page_header the renderable.
*/
public function get_header(): course_tools_page_header {
return $this->coursetoolspageheader;
}
/**
* Get the course tools list system report.
*
* @return course_external_tools_list the course tools list report.
*/
public function get_table(): course_external_tools_list {
return $this->coursetoolsreport;
}
}
@@ -0,0 +1,62 @@
<?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/>.
namespace mod_lti\output;
use core\output\notification;
use renderer_base;
/**
* Course tools page header renderable, containing the data for the page zero state and 'add tool' button.
*
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_tools_page_header implements \templatable {
/**
* Constructor.
*
* @param int $courseid the course id.
* @param int $toolcount the number of tools available in the course.
* @param bool $canadd whether the user can add tools to the course or not.
*/
public function __construct(protected int $courseid, protected int $toolcount, protected bool $canadd) {
}
/**
* Export the header's data for template use.
*
* @param renderer_base $output
* @return object the data.
*/
public function export_for_template(renderer_base $output): \stdClass {
$context = (object) [];
if ($this->canadd) {
$context->addlink = (new \moodle_url('/mod/lti/coursetooledit.php', ['course' => $this->courseid]))->out();
}
if ($this->toolcount == 0) {
$notification = new notification(get_string('nocourseexternaltoolsnotice', 'mod_lti'), notification::NOTIFY_INFO, true);
$context->notoolsnotice = $notification->export_for_template($output);
}
return $context;
}
}
@@ -0,0 +1,50 @@
<?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/>.
/**
* Class containing data for external registration return page.
*
* @package mod_lti
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\output;
require_once($CFG->dirroot.'/mod/lti/locallib.php');
use renderable;
use templatable;
use renderer_base;
use stdClass;
/**
* Class containing data for tool_configure page
*
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external_registration_return_page implements renderable, templatable {
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output The renderer
* @return stdClass Data to be used by the template
*/
public function export_for_template(renderer_base $output) {
return new stdClass();
}
}
@@ -0,0 +1,75 @@
<?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/>.
/**
* Class containing data for rendering LTI upgrade choices page.
*
* @copyright 2021 Cengage
* @package mod_lti
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\output;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/mod/lti/locallib.php');
use renderable;
use templatable;
use renderer_base;
use stdClass;
/**
* Class containing data for rendering LTI upgrade choices page.
*
* @copyright 2021 Cengage
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class registration_upgrade_choice_page implements renderable, templatable {
/** @var array array of tools. */
protected array $tools = [];
/** @var string tool URL. */
protected string $startregurl;
/**
* Constructor
*
* @param array $tools array of tools that can be upgraded
* @param string $startregurl tool URL to start the registration process
*/
public function __construct(array $tools, string $startregurl) {
$this->tools = $tools;
$this->startregurl = $startregurl;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output The renderer
* @return stdClass Data to be used by the template
*/
public function export_for_template(renderer_base $output) {
$renderdata = new stdClass();
$renderdata->startregurlenc = urlencode($this->startregurl);
$renderdata->sesskey = sesskey();
$renderdata->tools = [];
foreach ($this->tools as $tool) {
$renderdata->tools[] = (object)$tool;
}
return $renderdata;
}
}
+103
View File
@@ -0,0 +1,103 @@
<?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/>.
/**
* Renderer class for template library.
*
* @package mod_lti
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\output;
defined('MOODLE_INTERNAL') || die;
use plugin_renderer_base;
/**
* Renderer class for template library.
*
* @package mod_lti
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Defer to template.
*
* @param tool_configure_page $page
*
* @return string html for the page
*/
public function render_tool_configure_page($page) {
$data = $page->export_for_template($this);
return parent::render_from_template('mod_lti/tool_configure', $data);
}
/**
* Render the external registration return page
*
* @param tool_configure_page $page
*
* @return string html for the page
*/
public function render_external_registration_return_page($page) {
$data = $page->export_for_template($this);
return parent::render_from_template('mod_lti/external_registration_return', $data);
}
/**
* Render the external registration return page
*
* @param tool_configure_page $page
*
* @return string html for the page
*/
public function render_registration_upgrade_choice_page($page) {
$data = $page->export_for_template($this);
return parent::render_from_template('mod_lti/registration_upgrade_choice_page', $data);
}
/**
* Render the reposting of the cross site request.
*
* @param repost_crosssite_page $page the page renderable.
*
* @return string rendered html for the page.
*/
public function render_repost_crosssite_page(repost_crosssite_page $page): string {
$data = $page->export_for_template($this);
return parent::render_from_template('mod_lti/repost_crosssite', $data);
}
/**
* Render the course tools page header.
*
* @param course_tools_page $page the page renderable.
* @return string the rendered html for the page.
*/
protected function render_course_tools_page(course_tools_page $page): string {
// Render the table header templatable + the report.
$headerrenderable = $page->get_header();
$table = $page->get_table();
$headercontext = $headerrenderable->export_for_template($this);
$headeroutput = parent::render_from_template('mod_lti/course_tools_page_header', $headercontext);
return $headeroutput . $table->output();
}
}
@@ -0,0 +1,82 @@
<?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/>.
/**
* Render a page containing a simple form which reposts to self via JS.
*
* The purpose of this form is to resend a cross-site request to self, which allows the browsers to include the Moodle
* session cookie alongside the original POST data, allowing LTI flows to function despite browsers blocking
* cross-site cookies.
*
* @copyright 2021 Cengage
* @package mod_lti
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\output;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/mod/lti/locallib.php');
use renderable;
use templatable;
use renderer_base;
use stdClass;
/**
* Render a page containing a simple form which reposts to self via JS.
*
* The purpose of this form is to resend a cross-site request to self, which allows the browsers to include the Moodle
* session cookie alongside the original POST data, allowing LTI flows to function despite browsers blocking
* cross-site cookies.
*
* @copyright 2021 Cengage
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class repost_crosssite_page implements renderable, templatable {
/** @var array POST params. */
protected $params;
/** @var string URL to repost to. */
protected string $url;
/**
* Constructor
*
* @param string $url moodle URL to repost to
* @param array $post the POST params to be re-posted
*/
public function __construct(string $url, array $post) {
$this->params = array_map(function($k) use ($post) {
return ["key" => $k, "value" => $post[$k]];
}, array_keys($post));
$this->url = $url;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output The renderer
* @return stdClass Data to be used by the template
*/
public function export_for_template(renderer_base $output) {
$renderdata = new stdClass();
$renderdata->url = $this->url;
$renderdata->params = $this->params;
return $renderdata;
}
}
@@ -0,0 +1,66 @@
<?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/>.
/**
* Class containing data for tool_configure page
*
* @package mod_lti
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\output;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/mod/lti/locallib.php');
use moodle_url;
use renderable;
use templatable;
use renderer_base;
use stdClass;
use help_icon;
/**
* Class containing data for tool_configure page
*
* @copyright 2015 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_configure_page implements renderable, templatable {
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output The renderer
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
$data = new stdClass();
$keyhelp = new help_icon('resourcekey', 'mod_lti');
$secrethelp = new help_icon('password', 'mod_lti');
$url = new moodle_url('/mod/lti/typessettings.php', array('sesskey' => sesskey(), 'returnto' => 'toolconfigure'));
$data->configuremanualurl = $url->out();
$url = new moodle_url('/admin/settings.php?section=modsettinglti');
$data->managetoolsurl = $url->out();
$url = new moodle_url('/mod/lti/toolproxies.php');
$data->managetoolproxiesurl = $url->out();
$data->keyhelp = $keyhelp->export_for_template($output);
$data->secrethelp = $secrethelp->export_for_template($output);
return $data;
}
}
+59
View File
@@ -0,0 +1,59 @@
<?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/>.
/**
* LTI service plugin info.
*
* @package mod_lti
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @author Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\plugininfo;
use core\plugininfo\base;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_lti\plugininfo\ltiservice class.
*
* @package mod_lti
* @since Moodle 2.8
* @copyright 2014 Vital Source Technologies http://vitalsource.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class ltiservice extends base {
/**
* Should there be a way to uninstall the plugin via the administration UI?
*
* Uninstallation is not allowed for core subplugins.
*
* @return boolean
*/
public function is_uninstall_allowed() {
if ($this->is_standard()) {
return false;
}
return true;
}
}
+81
View File
@@ -0,0 +1,81 @@
<?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/>.
/**
* LTI source plugin info.
*
* @package mod_lti
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\plugininfo;
use core\plugininfo\base;
defined('MOODLE_INTERNAL') || die();
class ltisource extends base {
/**
* Returns the node name used in admin settings menu for this plugin settings (if applicable)
*
* @return null|string node name or null if plugin does not create settings node (default)
*/
public function get_settings_section_name() {
return 'ltisourcesetting'.$this->name;
}
/**
* Loads plugin settings to the settings tree
*
* This function usually includes settings.php file in plugins folder.
* Alternatively it can create a link to some settings page (instance of admin_externalpage)
*
* @param \part_of_admin_tree $adminroot
* @param string $parentnodename
* @param bool $hassiteconfig whether the current user has moodle/site:config capability
*/
public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
$ADMIN = $adminroot; // May be used in settings.php.
$plugininfo = $this; // Also can be used inside settings.php.
if (!$this->is_installed_and_upgraded()) {
return;
}
if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) {
return;
}
$section = $this->get_settings_section_name();
$settings = new \admin_settingpage($section, $this->displayname,
'moodle/site:config', $this->is_enabled() === false);
include($this->full_path('settings.php')); // This may also set $settings to null.
if ($settings) {
$ADMIN->add($parentnodename, $settings);
}
}
/**
* Should there be a way to uninstall the plugin via the administration UI.
*
* @return bool
*/
public function is_uninstall_allowed() {
return true;
}
}
+465
View File
@@ -0,0 +1,465 @@
<?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/>.
/**
* Privacy Subsystem implementation for mod_lti.
*
* @package mod_lti
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem implementation for mod_lti.
*
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider {
/**
* Return the fields which contain personal data.
*
* @param collection $items a reference to the collection to use to store the metadata.
* @return collection the updated collection of metadata items.
*/
public static function get_metadata(collection $items): collection {
$items->add_external_location_link(
'lti_provider',
[
'userid' => 'privacy:metadata:userid',
'username' => 'privacy:metadata:username',
'useridnumber' => 'privacy:metadata:useridnumber',
'firstname' => 'privacy:metadata:firstname',
'lastname' => 'privacy:metadata:lastname',
'fullname' => 'privacy:metadata:fullname',
'email' => 'privacy:metadata:email',
'role' => 'privacy:metadata:role',
'courseid' => 'privacy:metadata:courseid',
'courseidnumber' => 'privacy:metadata:courseidnumber',
'courseshortname' => 'privacy:metadata:courseshortname',
'coursefullname' => 'privacy:metadata:coursefullname',
],
'privacy:metadata:externalpurpose'
);
$items->add_database_table(
'lti_submission',
[
'userid' => 'privacy:metadata:lti_submission:userid',
'datesubmitted' => 'privacy:metadata:lti_submission:datesubmitted',
'dateupdated' => 'privacy:metadata:lti_submission:dateupdated',
'gradepercent' => 'privacy:metadata:lti_submission:gradepercent',
'originalgrade' => 'privacy:metadata:lti_submission:originalgrade',
],
'privacy:metadata:lti_submission'
);
$items->add_database_table(
'lti_tool_proxies',
[
'name' => 'privacy:metadata:lti_tool_proxies:name',
'createdby' => 'privacy:metadata:createdby',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified'
],
'privacy:metadata:lti_tool_proxies'
);
$items->add_database_table(
'lti_types',
[
'name' => 'privacy:metadata:lti_types:name',
'createdby' => 'privacy:metadata:createdby',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified'
],
'privacy:metadata:lti_types'
);
return $items;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid the userid.
* @return contextlist the list of contexts containing user info for the user.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
// Fetch all LTI submissions.
$sql = "SELECT c.id
FROM {context} c
INNER JOIN {course_modules} cm
ON cm.id = c.instanceid
AND c.contextlevel = :contextlevel
INNER JOIN {modules} m
ON m.id = cm.module
AND m.name = :modname
INNER JOIN {lti} lti
ON lti.id = cm.instance
INNER JOIN {lti_submission} ltisub
ON ltisub.ltiid = lti.id
WHERE ltisub.userid = :userid";
$params = [
'modname' => 'lti',
'contextlevel' => CONTEXT_MODULE,
'userid' => $userid,
];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
// Fetch all LTI types.
$sql = "SELECT c.id
FROM {context} c
JOIN {course} course
ON c.contextlevel = :contextlevel
AND c.instanceid = course.id
JOIN {lti_types} ltit
ON ltit.course = course.id
WHERE ltit.createdby = :userid";
$params = [
'contextlevel' => CONTEXT_COURSE,
'userid' => $userid
];
$contextlist->add_from_sql($sql, $params);
// The LTI tool proxies sit in the system context.
$contextlist->add_system_context();
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!is_a($context, \context_module::class)) {
return;
}
// Fetch all LTI submissions.
$sql = "SELECT ltisub.userid
FROM {context} c
INNER JOIN {course_modules} cm
ON cm.id = c.instanceid
AND c.contextlevel = :contextlevel
INNER JOIN {modules} m
ON m.id = cm.module
AND m.name = :modname
INNER JOIN {lti} lti
ON lti.id = cm.instance
INNER JOIN {lti_submission} ltisub
ON ltisub.ltiid = lti.id
WHERE c.id = :contextid";
$params = [
'modname' => 'lti',
'contextlevel' => CONTEXT_MODULE,
'contextid' => $context->id,
];
$userlist->add_from_sql('userid', $sql, $params);
// Fetch all LTI types.
$sql = "SELECT ltit.createdby AS userid
FROM {context} c
JOIN {course} course
ON c.contextlevel = :contextlevel
AND c.instanceid = course.id
JOIN {lti_types} ltit
ON ltit.course = course.id
WHERE c.id = :contextid";
$params = [
'contextlevel' => CONTEXT_COURSE,
'contextid' => $context->id,
];
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
*
* @param approved_contextlist $contextlist a list of contexts approved for export.
*/
public static function export_user_data(approved_contextlist $contextlist) {
self::export_user_data_lti_submissions($contextlist);
self::export_user_data_lti_types($contextlist);
self::export_user_data_lti_tool_proxies($contextlist);
}
/**
* Delete all data for all users in the specified context.
*
* @param \context $context the context to delete in.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
if (!$context instanceof \context_module) {
return;
}
if ($cm = get_coursemodule_from_id('lti', $context->instanceid)) {
$DB->delete_records('lti_submission', ['ltiid' => $cm->instance]);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist a list of contexts approved for deletion.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
if (empty($contextlist->count())) {
return;
}
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
if (!$context instanceof \context_module) {
continue;
}
$instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
$DB->delete_records('lti_submission', ['ltiid' => $instanceid, 'userid' => $userid]);
}
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
if ($context instanceof \context_module) {
$instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
list($insql, $inparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$sql = "ltiid = :instanceid AND userid {$insql}";
$params = array_merge(['instanceid' => $instanceid], $inparams);
$DB->delete_records_select('lti_submission', $sql, $params);
}
}
/**
* Export personal data for the given approved_contextlist related to LTI submissions.
*
* @param approved_contextlist $contextlist a list of contexts approved for export.
*/
protected static function export_user_data_lti_submissions(approved_contextlist $contextlist) {
global $DB;
// Filter out any contexts that are not related to modules.
$cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_MODULE) {
$carry[] = $context->instanceid;
}
return $carry;
}, []);
if (empty($cmids)) {
return;
}
$user = $contextlist->get_user();
// Get all the LTI activities associated with the above course modules.
$ltiidstocmids = self::get_lti_ids_to_cmids_from_cmids($cmids);
$ltiids = array_keys($ltiidstocmids);
list($insql, $inparams) = $DB->get_in_or_equal($ltiids, SQL_PARAMS_NAMED);
$params = array_merge($inparams, ['userid' => $user->id]);
$recordset = $DB->get_recordset_select('lti_submission', "ltiid $insql AND userid = :userid", $params, 'dateupdated, id');
self::recordset_loop_and_export($recordset, 'ltiid', [], function($carry, $record) use ($user, $ltiidstocmids) {
$carry[] = [
'gradepercent' => $record->gradepercent,
'originalgrade' => $record->originalgrade,
'datesubmitted' => transform::datetime($record->datesubmitted),
'dateupdated' => transform::datetime($record->dateupdated)
];
return $carry;
}, function($ltiid, $data) use ($user, $ltiidstocmids) {
$context = \context_module::instance($ltiidstocmids[$ltiid]);
$contextdata = helper::get_context_data($context, $user);
$finaldata = (object) array_merge((array) $contextdata, ['submissions' => $data]);
helper::export_context_files($context, $user);
writer::with_context($context)->export_data([], $finaldata);
});
}
/**
* Export personal data for the given approved_contextlist related to LTI types.
*
* @param approved_contextlist $contextlist a list of contexts approved for export.
*/
protected static function export_user_data_lti_types(approved_contextlist $contextlist) {
global $DB;
// Filter out any contexts that are not related to courses.
$courseids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_COURSE) {
$carry[] = $context->instanceid;
}
return $carry;
}, []);
if (empty($courseids)) {
return;
}
$user = $contextlist->get_user();
list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
$params = array_merge($inparams, ['userid' => $user->id]);
$ltitypes = $DB->get_recordset_select('lti_types', "course $insql AND createdby = :userid", $params, 'timecreated ASC');
self::recordset_loop_and_export($ltitypes, 'course', [], function($carry, $record) {
$context = \context_course::instance($record->course);
$options = ['context' => $context];
$carry[] = [
'name' => format_string($record->name, true, $options),
'createdby' => transform::user($record->createdby),
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => transform::datetime($record->timemodified)
];
return $carry;
}, function($courseid, $data) {
$context = \context_course::instance($courseid);
$finaldata = (object) ['lti_types' => $data];
writer::with_context($context)->export_data([], $finaldata);
});
}
/**
* Export personal data for the given approved_contextlist related to LTI tool proxies.
*
* @param approved_contextlist $contextlist a list of contexts approved for export.
*/
protected static function export_user_data_lti_tool_proxies(approved_contextlist $contextlist) {
global $DB;
// Filter out any contexts that are not related to system context.
$systemcontexts = array_filter($contextlist->get_contexts(), function($context) {
return $context->contextlevel == CONTEXT_SYSTEM;
});
if (empty($systemcontexts)) {
return;
}
$user = $contextlist->get_user();
$systemcontext = \context_system::instance();
$data = [];
$ltiproxies = $DB->get_recordset('lti_tool_proxies', ['createdby' => $user->id], 'timecreated ASC');
foreach ($ltiproxies as $ltiproxy) {
$data[] = [
'name' => format_string($ltiproxy->name, true, ['context' => $systemcontext]),
'createdby' => transform::user($ltiproxy->createdby),
'timecreated' => transform::datetime($ltiproxy->timecreated),
'timemodified' => transform::datetime($ltiproxy->timemodified)
];
}
$ltiproxies->close();
$finaldata = (object) ['lti_tool_proxies' => $data];
writer::with_context($systemcontext)->export_data([], $finaldata);
}
/**
* Return a dict of LTI IDs mapped to their course module ID.
*
* @param array $cmids The course module IDs.
* @return array In the form of [$ltiid => $cmid].
*/
protected static function get_lti_ids_to_cmids_from_cmids(array $cmids) {
global $DB;
list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
$sql = "SELECT lti.id, cm.id AS cmid
FROM {lti} lti
JOIN {modules} m
ON m.name = :lti
JOIN {course_modules} cm
ON cm.instance = lti.id
AND cm.module = m.id
WHERE cm.id $insql";
$params = array_merge($inparams, ['lti' => 'lti']);
return $DB->get_records_sql_menu($sql, $params);
}
/**
* Loop and export from a recordset.
*
* @param \moodle_recordset $recordset The recordset.
* @param string $splitkey The record key to determine when to export.
* @param mixed $initial The initial data to reduce from.
* @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
* @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
* @return void
*/
protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
callable $reducer, callable $export) {
$data = $initial;
$lastid = null;
foreach ($recordset as $record) {
if ($lastid && $record->{$splitkey} != $lastid) {
$export($lastid, $data);
$data = $initial;
}
$data = $reducer($data, $record);
$lastid = $record->{$splitkey};
}
$recordset->close();
if (!empty($lastid)) {
$export($lastid, $data);
}
}
}
@@ -0,0 +1,187 @@
<?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/>.
namespace mod_lti\reportbuilder\local\entities;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\text;
use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* Course external tools entity class implementation.
*
* Defines all the columns and filters that can be added to reports that use this entity.
*
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_types extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'lti_types',
'lti',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('entitycourseexternaltools', 'mod_lti');
}
/**
* Initialize the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this->add_filter($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$tablealias = $this->get_table_alias('lti_types');
// Name column.
$columns[] = (new column(
'name',
new lang_string('name', 'core'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.name, {$tablealias}.icon")
->set_is_sortable(true)
->add_callback(static function(string $name, \stdClass $data) {
global $OUTPUT;
$iconurl = $data->icon ?: $OUTPUT->image_url('monologo', 'lti')->out();
$iconclass = $data->icon ? ' nofilter' : '';
$iconcontainerclass = 'activityiconcontainer smaller';
$name = $data->name;
$img = \html_writer::img($iconurl, get_string('courseexternaltooliconalt', 'mod_lti', $name),
['class' => 'activityicon' . $iconclass]);
$name = \html_writer::span($name, 'align-self-center');
return \html_writer::div(\html_writer::div($img, 'mr-2 '.$iconcontainerclass) . $name, 'd-flex');
});
// Description column.
$columns[] = (new column(
'description',
new lang_string('description', 'core'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$tablealias}.description")
->set_is_sortable(true);
// Course column.
$columns[] = (new column(
'course',
new lang_string('course', 'core'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_field("{$tablealias}.course")
->set_is_sortable(true);
// LTI Version column.
$columns[] = (new column(
'ltiversion',
new lang_string('version'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$tablealias}.ltiversion")
->set_is_sortable(true);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$tablealias = $this->get_table_alias('lti_types');
return [
// Name filter.
(new filter(
text::class,
'name',
new lang_string('name'),
$this->get_entity_name(),
"{$tablealias}.name"
))
->add_joins($this->get_joins()),
// Description filter.
(new filter(
text::class,
'description',
new lang_string('description'),
$this->get_entity_name(),
"{$tablealias}.description"
))
->add_joins($this->get_joins()),
// LTI Version filter.
(new filter(
select::class,
'ltiversion',
new lang_string('version'),
$this->get_entity_name(),
"{$tablealias}.ltiversion"
))
->add_joins($this->get_joins())
->set_options_callback(static function(): array {
return ['LTI-1p0' => 'Legacy LTI', '1.3.0' => "LTI Advantage"];
})
];
}
}
@@ -0,0 +1,270 @@
<?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/>.
namespace mod_lti\reportbuilder\local\systemreports;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\report\column;
use mod_lti\reportbuilder\local\entities\tool_types;
use core_reportbuilder\system_report;
/**
* Course external tools list system report class implementation.
*
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_external_tools_list extends system_report {
/** @var \stdClass the course to constrain the report to. */
protected \stdClass $course;
/** @var int the usage count for the tool represented in a row, and set by row_callback(). */
protected int $perrowtoolusage = 0;
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
protected function initialise(): void {
global $DB, $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
$this->course = get_course($this->get_context()->instanceid);
// Our main entity, it contains all the column definitions that we need.
$entitymain = new tool_types();
$entitymainalias = $entitymain->get_table_alias('lti_types');
$this->set_main_table('lti_types', $entitymainalias);
$this->add_entity($entitymain);
// Now we can call our helper methods to add the content we want to include in the report.
$this->add_columns($entitymain);
$this->add_filters();
$this->add_actions();
// We need id and course in the actions column, without entity prefixes, so add these here.
// We also need access to the tool usage count in a few places (the usage column as well as the actions column).
$ti = database::generate_param_name(); // Tool instance param.
$this->add_base_fields("{$entitymainalias}.id, {$entitymainalias}.course, ".
"(SELECT COUNT($ti.id)
FROM {lti} $ti
WHERE $ti.typeid = {$entitymainalias}.id) AS toolusage");
// Join the types_categories table, to include only tools available to the current course's category.
$cattablealias = database::generate_alias();
$joinsql = "LEFT JOIN {lti_types_categories} {$cattablealias}
ON ({$cattablealias}.typeid = {$entitymainalias}.id)";
$this->add_join($joinsql);
// Scope the report to the course context and include only those tools available to the category.
$paramprefix = database::generate_param_name();
$coursevisibleparam = database::generate_param_name();
$categoryparam = database::generate_param_name();
$toolstateparam = database::generate_param_name();
[$insql, $params] = $DB->get_in_or_equal([get_site()->id, $this->course->id], SQL_PARAMS_NAMED, "{$paramprefix}_");
$wheresql = "{$entitymainalias}.course {$insql} ".
"AND {$entitymainalias}.coursevisible NOT IN (:{$coursevisibleparam}) ".
"AND ({$cattablealias}.id IS NULL OR {$cattablealias}.categoryid = :{$categoryparam}) ".
"AND {$entitymainalias}.state = :{$toolstateparam}";
$params = array_merge(
$params,
[
$coursevisibleparam => LTI_COURSEVISIBLE_NO,
$categoryparam => $this->course->category,
$toolstateparam => LTI_TOOL_STATE_CONFIGURED
]
);
$this->add_base_condition_sql($wheresql, $params);
$this->set_downloadable(false, get_string('pluginname', 'mod_lti'));
$this->set_default_per_page(10);
$this->set_default_no_results_notice(null);
}
/**
* Validates access to view this report
*
* @return bool
*/
protected function can_view(): bool {
return has_capability('mod/lti:addpreconfiguredinstance', $this->get_context());
}
public function row_callback(\stdClass $row): void {
$this->perrowtoolusage = $row->toolusage;
}
/**
* Adds the columns we want to display in the report.
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
* @param tool_types $tooltypesentity
* @return void
*/
protected function add_columns(tool_types $tooltypesentity): void {
$entitymainalias = $tooltypesentity->get_table_alias('lti_types');
$columns = [
'tool_types:name',
'tool_types:description',
];
$this->add_columns_from_entities($columns);
// Tool usage column using a custom SQL subquery (defined in initialise method) to count tool instances within the course.
// TODO: This should be replaced with proper column aggregation once that's added to system_report instances in MDL-76392.
$this->add_column(new column(
'usage',
new \lang_string('usage', 'mod_lti'),
$tooltypesentity->get_entity_name()
))
->set_type(column::TYPE_INTEGER)
->set_is_sortable(true)
->add_field("{$entitymainalias}.id")
->add_callback(fn() => $this->perrowtoolusage);
// Enable toggle column.
$this->add_column((new column(
'showinactivitychooser',
new \lang_string('showinactivitychooser', 'mod_lti'),
$tooltypesentity->get_entity_name()
))
// Site tools can be overridden on course level.
->add_join("LEFT JOIN {lti_coursevisible} lc ON lc.typeid = {$entitymainalias}.id AND lc.courseid = " . $this->course->id)
->set_type(column::TYPE_INTEGER)
->add_fields("{$entitymainalias}.id, {$entitymainalias}.coursevisible, lc.coursevisible as coursevisibleoverridden")
->set_is_sortable(false)
->set_callback(function(int $id, \stdClass $row): string {
global $PAGE;
$coursevisible = $row->coursevisible;
$courseid = $this->course->id;
if (!empty($row->coursevisibleoverridden)) {
$coursevisible = $row->coursevisibleoverridden;
}
if ($coursevisible == LTI_COURSEVISIBLE_ACTIVITYCHOOSER) {
$coursevisible = true;
} else {
$coursevisible = false;
}
$renderer = $PAGE->get_renderer('core_reportbuilder');
$attributes = [
['name' => 'id', 'value' => $row->id],
['name' => 'courseid', 'value' => $courseid],
['name' => 'action', 'value' => 'showinactivitychooser-toggle'],
['name' => 'state', 'value' => $coursevisible],
];
$label = $coursevisible ? get_string('dontshowinactivitychooser', 'mod_lti')
: get_string('showinactivitychooser', 'mod_lti');
$disabled = !has_capability('mod/lti:addcoursetool', \context_course::instance($courseid));
return $renderer->render_from_template('core/toggle', [
'id' => 'showinactivitychooser-toggle-' . $row->id,
'checked' => $coursevisible,
'disabled' => $disabled,
'dataattributes' => $attributes,
'label' => $label,
'labelclasses' => 'sr-only'
]);
})
);
// Attempt to create a dummy actions column, working around the limitations of the official actions feature.
$this->add_column(new column(
'actions', new \lang_string('actions'),
$tooltypesentity->get_entity_name()
))
->set_type(column::TYPE_TEXT)
->set_is_sortable(false)
->add_fields("{$entitymainalias}.id, {$entitymainalias}.course, {$entitymainalias}.name")
->add_callback(function($field, $row) {
global $OUTPUT;
// Lock actions for site-level preconfigured tools.
if (get_site()->id == $row->course) {
return \html_writer::div(
\html_writer::div(
$OUTPUT->pix_icon('t/locked', get_string('courseexternaltoolsnoeditpermissions', 'mod_lti')
), 'tool-action-icon-container'), 'd-flex justify-content-end'
);
}
// Lock actions when the user can't add course tools.
if (!has_capability('mod/lti:addcoursetool', \context_course::instance($row->course))) {
return \html_writer::div(
\html_writer::div(
$OUTPUT->pix_icon('t/locked', get_string('courseexternaltoolsnoeditpermissions', 'mod_lti')
), 'tool-action-icon-container'), 'd-flex justify-content-end'
);
}
// Build and display an action menu.
$menu = new \action_menu();
$menu->set_menu_trigger($OUTPUT->pix_icon('i/moremenu', get_string('actions', 'core')),
'btn btn-icon d-flex align-items-center justify-content-center'); // TODO check 'actions' lang string with UX.
$menu->add(new \action_menu_link(
new \moodle_url('/mod/lti/coursetooledit.php', ['course' => $row->course, 'typeid' => $row->id]),
null,
get_string('edit', 'core'),
null
));
$menu->add(new \action_menu_link(
new \moodle_url('#'),
null,
get_string('delete', 'core'),
null,
[
'data-action' => 'course-tool-delete',
'data-course-tool-id' => $row->id,
'data-course-tool-name' => $row->name,
'data-course-tool-usage' => $this->perrowtoolusage,
'class' => 'text-danger',
],
));
return $OUTPUT->render($menu);
});
// Default sorting.
$this->set_initial_sort_column('tool_types:name', SORT_ASC);
}
/**
* Add any actions for this report.
*
* @return void
*/
protected function add_actions(): void {
}
/**
* Adds the filters we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
protected function add_filters(): void {
$this->add_filters_from_entities([]);
}
}
+46
View File
@@ -0,0 +1,46 @@
<?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/>.
/**
* Search area for mod_lti activities.
*
* @package mod_lti
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\search;
defined('MOODLE_INTERNAL') || die();
/**
* Search area for mod_lti activities.
*
* @package mod_lti
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity extends \core_search\base_activity {
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return true;
}
}
@@ -0,0 +1,122 @@
<?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/>.
/**
* Exception handler for LTI services
*
* @package mod_lti
* @copyright Copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/../locallib.php');
require_once(__DIR__.'/../servicelib.php');
/**
* Handles exceptions when handling incoming LTI messages.
*
* Ensures that LTI always returns a XML message that can be consumed by the caller.
*
* @package mod_lti
* @copyright Copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class service_exception_handler {
/**
* Enable error response logging.
*
* @var bool
*/
protected $log = false;
/**
* The LTI service message ID, if known.
*
* @var string
*/
protected $id = '';
/**
* The LTI service message type, if known.
*
* @var string
*/
protected $type = 'unknownRequest';
/**
* Constructor.
*
* @param boolean $log Enable error response logging.
*/
public function __construct($log) {
$this->log = $log;
}
/**
* Set the LTI message ID being handled.
*
* @param string $id
*/
public function set_message_id($id) {
if (!empty($id)) {
$this->id = $id;
}
}
/**
* Set the LTI message type being handled.
*
* @param string $type
*/
public function set_message_type($type) {
if (!empty($type)) {
$this->type = $type;
}
}
/**
* Echo an exception message encapsulated in XML.
*
* @param \Exception|\Throwable $exception The exception that was thrown
*/
public function handle($exception) {
$message = $exception->getMessage();
// Add the exception backtrace for developers.
if (debugging('', DEBUG_DEVELOPER)) {
$message .= "\n".format_backtrace(get_exception_info($exception)->backtrace, true);
}
// Switch to response.
$type = str_replace('Request', 'Response', $this->type);
// Build the appropriate xml.
$response = lti_get_response_xml('failure', $message, $this->id, $type);
$xml = $response->asXML();
// Log the request if necessary.
if ($this->log) {
lti_log_response($xml, $exception);
}
echo $xml;
}
}
@@ -0,0 +1,56 @@
<?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/>.
/**
* A scheduled task for lti module.
*
* @package mod_lti
* @copyright 2019 Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\task;
use core\task\scheduled_task;
defined('MOODLE_INTERNAL') || die();
/**
* Class containing the scheduled task for lti module.
*
* @package mod_lti
* @copyright 2018 Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class clean_access_tokens extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('cleanaccesstokens', 'mod_lti');
}
/**
* Run lti cron.
*/
public function execute() {
global $DB;
$DB->delete_records_select('lti_access_tokens', 'validuntil < ?', [time()]);
}
}
+66
View File
@@ -0,0 +1,66 @@
<?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/>.
/**
* Handle sending a user to a tool provider to initiate a content-item selection.
*
* @package mod_lti
* @copyright 2015 Vital Source Technologies http://vitalsource.com
* @author Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_once($CFG->dirroot . '/mod/lti/lib.php');
require_once($CFG->dirroot . '/mod/lti/locallib.php');
$id = required_param('id', PARAM_INT);
$courseid = required_param('course', PARAM_INT);
$title = optional_param('title', '', PARAM_TEXT);
$text = optional_param('text', '', PARAM_RAW);
$config = lti_get_type_type_config($id);
if ($config->lti_ltiversion === LTI_VERSION_1P3) {
if (!isset($SESSION->lti_initiatelogin_status)) {
echo lti_initiate_login($courseid, 0, null, $config, 'ContentItemSelectionRequest', $title, $text);
exit;
} else {
unset($SESSION->lti_initiatelogin_status);
}
}
// Check access and capabilities.
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
require_login($course);
$context = context_course::instance($courseid);
require_capability('moodle/course:manageactivities', $context);
require_capability('mod/lti:addcoursetool', $context);
// Set the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
$returnurlparams = [
'course' => $course->id,
'id' => $id,
'sesskey' => sesskey()
];
$returnurl = new \moodle_url('/mod/lti/contentitem_return.php', $returnurlparams);
// Prepare the request.
$request = lti_build_content_item_selection_request($id, $course, $returnurl, $title, $text, [], []);
// Get the launch HTML.
$content = lti_post_launch_html($request->params, $request->url, false);
echo $content;
+108
View File
@@ -0,0 +1,108 @@
<?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/>.
/**
* Handle the return from the Tool Provider after selecting a content item.
*
* @package mod_lti
* @copyright 2015 Vital Source Technologies http://vitalsource.com
* @author Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_once($CFG->dirroot . '/mod/lti/locallib.php');
$id = required_param('id', PARAM_INT);
$courseid = required_param('course', PARAM_INT);
$jwt = optional_param('JWT', '', PARAM_RAW);
$context = context_course::instance($courseid);
$pageurl = new moodle_url('/mod/lti/contentitem_return.php');
$PAGE->set_url($pageurl);
$PAGE->set_pagelayout('popup');
$PAGE->set_context($context);
// Cross-Site causes the cookie to be lost if not POSTed from same site.
global $_POST;
if (!empty($_POST["repost"])) {
// Unset the param so that LTI 1.1 signature validation passes.
unset($_POST["repost"]);
} else if (!isloggedin()) {
header_remove("Set-Cookie");
$output = $PAGE->get_renderer('mod_lti');
$page = new \mod_lti\output\repost_crosssite_page($_SERVER['REQUEST_URI'], $_POST);
echo $output->header();
echo $output->render($page);
echo $output->footer();
return;
}
if (!empty($jwt)) {
$params = lti_convert_from_jwt($id, $jwt);
$consumerkey = $params['oauth_consumer_key'] ?? '';
$messagetype = $params['lti_message_type'] ?? '';
$version = $params['lti_version'] ?? '';
$items = $params['content_items'] ?? '';
$errormsg = $params['lti_errormsg'] ?? '';
$msg = $params['lti_msg'] ?? '';
} else {
$consumerkey = required_param('oauth_consumer_key', PARAM_RAW);
$messagetype = required_param('lti_message_type', PARAM_TEXT);
$version = required_param('lti_version', PARAM_TEXT);
$items = optional_param('content_items', '', PARAM_RAW);
$errormsg = optional_param('lti_errormsg', '', PARAM_TEXT);
$msg = optional_param('lti_msg', '', PARAM_TEXT);
lti_verify_oauth_signature($id, $consumerkey);
}
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
require_login($course);
require_sesskey();
require_capability('moodle/course:manageactivities', $context);
require_capability('mod/lti:addcoursetool', $context);
$redirecturl = null;
$returndata = null;
if (empty($errormsg) && !empty($items)) {
try {
$returndata = lti_tool_configuration_from_content_item($id, $messagetype, $version, $consumerkey, $items);
} catch (moodle_exception $e) {
$errormsg = $e->getMessage();
}
}
echo $OUTPUT->header();
// Call JS module to redirect the user to the course page or close the dialogue on error/cancel.
$PAGE->requires->js_call_amd('mod_lti/contentitem_return', 'init', [$returndata]);
echo $OUTPUT->footer();
// Add messages to notification stack for rendering later.
if ($errormsg) {
// Content item selection has encountered an error.
\core\notification::error($errormsg);
} else if (!empty($returndata)) {
// Means success.
if (!$msg) {
$msg = get_string('successfullyfetchedtoolconfigurationfromcontent', 'lti');
}
\core\notification::success($msg);
}

Some files were not shown because too many files have changed in this diff Show More