diff --git a/lib/pluginlib.php b/lib/pluginlib.php index ad16ee9b44f..d80f8c50e5a 100644 --- a/lib/pluginlib.php +++ b/lib/pluginlib.php @@ -383,7 +383,7 @@ class plugin_manager { 'mod' => array( 'assignment', 'chat', 'choice', 'data', 'feedback', 'folder', - 'forum', 'glossary', 'imscp', 'label', 'lesson', 'page', + 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page', 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop' ), diff --git a/mod/lti/OAuth.php b/mod/lti/OAuth.php new file mode 100644 index 00000000000..47bb8cddf8f --- /dev/null +++ b/mod/lti/OAuth.php @@ -0,0 +1,841 @@ +. + +namespace moodle\mod\lti;//Using a namespace as the basicLTI module imports classes with the same names + +defined('MOODLE_INTERNAL') || die; + +$oauth_last_computed_signature = false; + +/* Generic exception class + */ +class OAuthException extends \Exception { + // pass +} + +class OAuthConsumer { + public $key; + public $secret; + + 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; + } +} + +class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { + function get_name() { + return "HMAC-SHA1"; + } + + public function build_signature($request, $consumer, $token) { + global $oauth_last_computed_signature; + $oauth_last_computed_signature = false; + + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + + $computed_signature = base64_encode(hash_hmac('sha1', $base_string, $key, true)); + $oauth_last_computed_signature = $computed_signature; + return $computed_signature; + } + +} + +class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { + public function get_name() { + 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 { + public function get_name() { + 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 Exception("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 Exception("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); + + // 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); + + // 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 = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? '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; + // Deal with magic_quotes + // http://www.php.net/manual/en/security.magicquotes.disabling.php + if (get_magic_quotes_gpc()) { + $outpost = array(); + foreach ($_POST as $k => $v) { + $v = stripslashes($v); + $ourpost[$k] = $v; + } + } + // 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 the url and rebuilds it to be + * scheme://host/path + */ + public function get_normalized_http_url() { + $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 $oauth_last_computed_signature; + $oauth_last_computed_signature = 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 $oauth_last_computed_signature; + $oauth_last_computed_signature = 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 ($oauth_last_computed_signature) { + $ex_text = $ex_text . " ours= $oauth_last_computed_signature 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 + return apache_request_headers(); + } + // 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); + } +} \ No newline at end of file diff --git a/mod/lti/OAuthBody.php b/mod/lti/OAuthBody.php new file mode 100644 index 00000000000..e5920d56585 --- /dev/null +++ b/mod/lti/OAuthBody.php @@ -0,0 +1,163 @@ +. + +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'); + +function getOAuthKeyFromHeaders() +{ + $request_headers = OAuthUtil::get_headers(); + // print_r($request_headers); + + if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") { + $header_parameters = OAuthUtil::split_header($request_headers['Authorization']); + + // echo("HEADER PARMS=\n"); + // print_r($header_parameters); + return $header_parameters['oauth_consumer_key']; + } + return false; +} + +function handleOAuthBodyPOST($oauth_consumer_key, $oauth_consumer_secret, $body, $request_headers = null) +{ + if($request_headers == null){ + $request_headers = OAuthUtil::get_headers(); + } + + // Must reject application/x-www-form-urlencoded + if ($request_headers['Content-type'] == 'application/x-www-form-urlencoded' ) { + throw new Exception("OAuth request body signing must not use application/x-www-form-urlencoded"); + } + + if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") { + $header_parameters = OAuthUtil::split_header($request_headers['Authorization']); + + // echo("HEADER PARMS=\n"); + // print_r($header_parameters); + $oauth_body_hash = $header_parameters['oauth_body_hash']; + // echo("OBH=".$oauth_body_hash."\n"); + } + + if ( ! isset($oauth_body_hash) ) { + throw new Exception("OAuth request body signing requires oauth_body_hash body"); + } + + // Verify the message signature + $store = new TrivialOAuthDataStore(); + $store->add_consumer($oauth_consumer_key, $oauth_consumer_secret); + + $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 Exception("OAuth signature failed: " . $message); + } + + $postdata = $body; + // echo($postdata); + + $hash = base64_encode(sha1($postdata, TRUE)); + + if ( $hash != $oauth_body_hash ) { + throw new Exception("OAuth oauth_body_hash mismatch"); + } + + return $postdata; +} + +function sendOAuthBodyPOST($method, $endpoint, $oauth_consumer_key, $oauth_consumer_secret, $content_type, $body) +{ + $hash = base64_encode(sha1($body, TRUE)); + + $parms = array('oauth_body_hash' => $hash); + + $test_token = ''; + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $test_consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL); + + $acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms); + $acc_req->sign_request($hmac_method, $test_consumer, $test_token); + + $header = $acc_req->to_header(); + $header = $header . "\r\nContent-type: " . $content_type . "\r\n"; + + $params = array('http' => array( + 'method' => 'POST', + 'content' => $body, + 'header' => $header + )); + $ctx = stream_context_create($params); + $fp = @fopen($endpoint, 'rb', false, $ctx); + if (!$fp) { + throw new Exception("Problem with $endpoint, $php_errormsg"); + } + $response = @stream_get_contents($fp); + if ($response === false) { + throw new Exception("Problem reading data from $endpoint, $php_errormsg"); + } + return $response; +} diff --git a/mod/lti/TrivialStore.php b/mod/lti/TrivialStore.php new file mode 100644 index 00000000000..afe686c3b8f --- /dev/null +++ b/mod/lti/TrivialStore.php @@ -0,0 +1,107 @@ +. + +/** + * This file contains a Trivial memory-based store - no support for tokens + * + * @package 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 { + private $consumers = array(); + + function add_consumer($consumer_key, $consumer_secret) { + $this->consumers[$consumer_key] = $consumer_secret; + } + + function lookup_consumer($consumer_key) { + if ( strpos($consumer_key, "http://" ) === 0 ) { + $consumer = new OAuthConsumer($consumer_key, "secret", null); + return $consumer; + } + if ( $this->consumers[$consumer_key] ) { + $consumer = new OAuthConsumer($consumer_key, $this->consumers[$consumer_key], null); + return $consumer; + } + return null; + } + + function lookup_token($consumer, $token_type, $token) { + return new OAuthToken($consumer, ""); + } + + // Return NULL if the nonce has not been used + // Return $nonce if the nonce was previously used + function lookup_nonce($consumer, $token, $nonce, $timestamp) { + // Should add some clever logic to keep nonces from + // being reused - for no we are really trusting + // that the timestamp will save us + return null; + } + + function new_request_token($consumer) { + return null; + } + + function new_access_token($token, $consumer) { + return null; + } +} diff --git a/mod/lti/ajax.php b/mod/lti/ajax.php new file mode 100644 index 00000000000..a7d8c30d736 --- /dev/null +++ b/mod/lti/ajax.php @@ -0,0 +1,53 @@ +. + +/** + * MRTODO: Brief description of this file + * + * @package mod + * @subpackage xml + * @copyright 2011 onwards MRTODO + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . "/../../config.php"); +require_once($CFG->dirroot . '/mod/lti/locallib.php'); + +$courseid = required_param('course', PARAM_INT); + +require_login($courseid, false); + +$action = required_param('action', PARAM_TEXT); + +$response = new stdClass(); + +switch ($action) { + case 'find_tool_config': + $toolurl = required_param('toolurl', PARAM_RAW); + + $tool = lti_get_tool_by_url_match($toolurl, $courseid); + + if (!empty($tool)) { + $response->toolid = $tool->id; + $response->toolname = htmlspecialchars($tool->name); + $response->tooldomain = htmlspecialchars($tool->tooldomain); + } + break; +} + +echo json_encode($response); + +die; diff --git a/mod/lti/backup/moodle1/lib.php b/mod/lti/backup/moodle1/lib.php new file mode 100644 index 00000000000..97f16558b9e --- /dev/null +++ b/mod/lti/backup/moodle1/lib.php @@ -0,0 +1,115 @@ +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)); + + $ignore_fields = array('id', 'modtype'); + if (!$DB->record_exists('lti_types', array('id' => $data['typeid']))) { + $ntypeid = $DB->get_field('lti_types_config', + 'typeid', + array('name' => 'toolurl', 'value' => $data['toolurl']), + IGNORE_MULTIPLE); + 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, $ignore_fields)) { + $this->xmlwriter->full_tag($field, $value); + } + } + + return $data; + } + + /** + * This is executed when we reach the closing 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(); + } + +} + diff --git a/mod/lti/backup/moodle2/backup_lti_activity_task.class.php b/mod/lti/backup/moodle2/backup_lti_activity_task.class.php new file mode 100644 index 00000000000..d4b6803ac0a --- /dev/null +++ b/mod/lti/backup/moodle2/backup_lti_activity_task.class.php @@ -0,0 +1,93 @@ +. +// +// 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 backup class + * + * @package mod + * @subpackage 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/backup_lti_stepslib.php'); + +/** + * lti backup task that provides all the settings and steps to perform one + * complete backup of the module + */ +class backup_lti_activity_task extends backup_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() { + // Choice only has one structure step + $this->add_step(new backup_lti_activity_structure_step('lti_structure', 'lti.xml')); + } + + /** + * Code the transformations to perform in the activity in + * order to get transportable (encoded) links + */ + static public 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; + } +} diff --git a/mod/lti/backup/moodle2/backup_lti_stepslib.php b/mod/lti/backup/moodle2/backup_lti_stepslib.php new file mode 100644 index 00000000000..3be251468f5 --- /dev/null +++ b/mod/lti/backup/moodle2/backup_lti_stepslib.php @@ -0,0 +1,100 @@ +. +// +// 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 + * @subpackage 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 { + + protected function define_structure() { + + // To know if we are including userinfo + $userinfo = $this->get_setting_value('userinfo'); + + // Define each element separated + $basiclti = new backup_nested_element('lti', array('id'), array( + 'name', + 'intro', + 'introformat', + 'timecreated', + 'timemodified', + 'typeid', + 'toolurl', + 'preferheight', + 'launchcontainer', + 'instructorchoicesendname', + 'instructorchoicesendemailaddr', + 'instructorchoiceacceptgrades', + 'instructorchoiceallowroster', + 'instructorchoiceallowsetting', + 'grade', + 'instructorcustomparameters', + 'showtitle', + 'showdescription' + ) + ); + + // Build the tree + // (none) + + // Define sources + $basiclti->set_source_table('lti', array('id' => backup::VAR_ACTIVITYID)); + + // Define id annotations + // (none) + + // Define file annotations + $basiclti->annotate_files('mod_lti', 'intro', null); // This file areas haven't itemid + + // Return the root element (basiclti), wrapped into standard activity structure + return $this->prepare_activity_structure($basiclti); + } +} diff --git a/mod/lti/backup/moodle2/restore_lti_activity_task.class.php b/mod/lti/backup/moodle2/restore_lti_activity_task.class.php new file mode 100644 index 00000000000..773fe2db4d0 --- /dev/null +++ b/mod/lti/backup/moodle2/restore_lti_activity_task.class.php @@ -0,0 +1,133 @@ +. +// +// 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 + * @subpackage 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'); // Because it exists (must) + +/** + * 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 + */ + static public 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 + */ + static public 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 + */ + static public 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) + */ + static public function define_restore_log_rules_for_course() { + $rules = array(); + + $rules[] = new restore_log_rule('lti', 'view all', 'index.php?id={course}', null); + + return $rules; + } +} diff --git a/mod/lti/backup/moodle2/restore_lti_stepslib.php b/mod/lti/backup/moodle2/restore_lti_stepslib.php new file mode 100644 index 00000000000..8589e2e129a --- /dev/null +++ b/mod/lti/backup/moodle2/restore_lti_stepslib.php @@ -0,0 +1,103 @@ +. +// +// 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 + * @subpackage 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; + +/** + * Structure step to restore one basiclti activity + */ +class restore_lti_activity_structure_step extends restore_activity_structure_step { + + protected function define_structure() { + + $paths = array(); + $paths[] = new restore_path_element('lti', '/activity/lti'); + + // Return the paths wrapped into standard activity structure + return $this->prepare_activity_structure($paths); + } + + protected function process_lti($data) { + global $DB, $CFG; + + $data = (object)$data; + $oldid = $data->id; + $data->course = $this->get_courseid(); + + require_once($CFG->dirroot.'/mod/lti/lib.php'); + + $newitemid = lti_add_instance($data, null); + + // insert the basiclti record + //$newitemid = $DB->insert_record('lti', $data); + // immediately after inserting "activity" record, call this + $this->apply_activity_instance($newitemid); + } + + protected function after_execute() { + global $DB; + + $basicltis = $DB->get_records('lti'); + foreach ($basicltis as $basiclti) { + if (!$DB->get_record('lti_types_config', + array('typeid' => $basiclti->typeid, 'name' => 'toolurl', 'value' => $basiclti->toolurl))) { + + $basiclti->typeid = 0; + } + + $basiclti->placementsecret = uniqid('', true); + $basiclti->timeplacementsecret = time(); + + $DB->update_record('lti', $basiclti); + } + + // Add basiclti related files, no need to match by itemname (just internally handled context) + $this->add_related_files('mod_lti', 'intro', null); + } +} diff --git a/mod/lti/basiclti.js b/mod/lti/basiclti.js new file mode 100644 index 00000000000..9fb6cece11e --- /dev/null +++ b/mod/lti/basiclti.js @@ -0,0 +1,56 @@ +// 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 . +// +// 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 library of javasxript functions for the lti module + * + * @package mod + * @subpackage lti + * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis + * marc.alier@upc.edu + * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu + * @author Marc Alier + * @author Jordi Piguillem + * @author Nikolas Galanis + * @author Charles Severance + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +function basicltiDebugToggle() { + var ele = document.getElementById('basicltiDebug'); + if (ele.style.display == 'block') { + ele.style.display = 'none'; + } else { + ele.style.display = 'block'; + } +} diff --git a/mod/lti/db/access.php b/mod/lti/db/access.php new file mode 100644 index 00000000000..8a5a3ac63e4 --- /dev/null +++ b/mod/lti/db/access.php @@ -0,0 +1,90 @@ +. + +/** + * This file contains the capabilities used by the lti module + * + * @package mod + * @subpackage 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; + +$capabilities = array( + + 'mod/lti:view' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'guest' => CAP_ALLOW, + 'student' => CAP_ALLOW, + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ) + ), + + 'mod/lti:grade' => array( + 'riskbitmask' => RISK_XSS, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ) + ), + + 'mod/lti:manage' => array( + 'riskbitmask' => RISK_XSS, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ) + ), + + 'mod/lti:addcoursetool' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ) + ), + + 'mod/lti:requesttooladd' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ) + ) +); diff --git a/mod/lti/db/install.xml b/mod/lti/db/install.xml new file mode 100644 index 00000000000..4d62cb08dbc --- /dev/null +++ b/mod/lti/db/install.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+
+
diff --git a/mod/lti/db/upgrade.php b/mod/lti/db/upgrade.php new file mode 100644 index 00000000000..99f9deb8739 --- /dev/null +++ b/mod/lti/db/upgrade.php @@ -0,0 +1,69 @@ +. +// +// 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 keeps track of upgrades to the lti module + * + * @package mod + * @subpackage 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; + +/** + * xmldb_lti_upgrade is the function that upgrades + * the lti module database when is needed + * + * This function is automaticly called when version number in + * version.php changes. + * + * @param int $oldversion New old version number. + * + * @return boolean + */ +function xmldb_lti_upgrade($oldversion) { + global $CFG, $DB; + + $dbman = $DB->get_manager(); + + return true; +} + diff --git a/mod/lti/edit_form.php b/mod/lti/edit_form.php new file mode 100644 index 00000000000..907ce56e5b8 --- /dev/null +++ b/mod/lti/edit_form.php @@ -0,0 +1,176 @@ +. +// +// 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 defines de main basiclti configuration form + * + * @package mod + * @subpackage lti + * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis + * marc.alier@upc.edu + * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu + * @author Marc Alier + * @author Jordi Piguillem + * @author Nikolas Galanis + * @author Charles Severance + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +require_once($CFG->libdir.'/formslib.php'); +require_once($CFG->dirroot.'/mod/lti/locallib.php'); + +class mod_lti_edit_types_form extends moodleform{ + public function definition() { + $mform =& $this->_form; + + //------------------------------------------------------------------------------- + // Add basiclti elements + $mform->addElement('header', 'setup', get_string('tool_settings', 'lti')); + + $mform->addElement('text', 'lti_typename', get_string('typename', 'lti')); + $mform->setType('lti_typename', PARAM_INT); + $mform->addHelpButton('lti_typename', 'typename', 'lti'); + $mform->addRule('lti_typename', null, 'required', null, 'client'); + + $mform->addElement('text', 'lti_toolurl', get_string('toolurl', 'lti'), array('size'=>'64')); + $mform->setType('lti_toolurl', PARAM_TEXT); + $mform->addHelpButton('lti_toolurl', 'toolurl', 'lti'); + $mform->addRule('lti_toolurl', null, 'required', null, 'client'); + + $mform->addElement('text', 'lti_resourcekey', get_string('resourcekey_admin', 'lti')); + $mform->setType('lti_resourcekey', PARAM_TEXT); + $mform->addHelpButton('lti_resourcekey', 'resourcekey_admin', 'lti'); + + $mform->addElement('passwordunmask', 'lti_password', get_string('password_admin', 'lti')); + $mform->setType('lti_password', PARAM_TEXT); + $mform->addHelpButton('lti_password', 'password_admin', 'lti'); + + $mform->addElement('textarea', 'lti_customparameters', get_string('custom', 'lti'), array('rows'=>4, 'cols'=>60)); + $mform->setType('lti_customparameters', PARAM_TEXT); + $mform->addHelpButton('lti_customparameters', 'custom', 'lti'); + + if (!empty($this->_customdata->isadmin)) { + $mform->addElement('checkbox', 'lti_coursevisible', ' ', ' ' . get_string('show_in_course', 'lti')); + $mform->addHelpButton('lti_coursevisible', 'show_in_course', 'lti'); + } else { + $mform->addElement('hidden', 'lti_coursevisible', '1'); + } + + $mform->addElement('hidden', 'typeid'); + + $launchoptions=array(); + $launchoptions[LTI_LAUNCH_CONTAINER_EMBED] = get_string('embed', 'lti'); + $launchoptions[LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS] = get_string('embed_no_blocks', 'lti'); + $launchoptions[LTI_LAUNCH_CONTAINER_WINDOW] = get_string('new_window', 'lti'); + + $mform->addElement('select', 'lti_launchcontainer', get_string('default_launch_container', 'lti'), $launchoptions); + $mform->setDefault('lti_launchcontainer', LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS); + $mform->addHelpButton('lti_launchcontainer', 'default_launch_container', 'lti'); + + // Add privacy preferences fieldset where users choose whether to send their data + $mform->addElement('header', 'privacy', get_string('privacy', 'lti')); + + $options=array(); + $options[0] = get_string('never', 'lti'); + $options[1] = get_string('always', 'lti'); + $options[2] = get_string('delegate', 'lti'); + + $mform->addElement('select', 'lti_sendname', get_string('share_name_admin', 'lti'), $options); + $mform->setDefault('lti_sendname', '2'); + $mform->addHelpButton('lti_sendname', 'share_name_admin', 'lti'); + + $mform->addElement('select', 'lti_sendemailaddr', get_string('share_email_admin', 'lti'), $options); + $mform->setDefault('lti_sendemailaddr', '2'); + $mform->addHelpButton('lti_sendemailaddr', 'share_email_admin', 'lti'); + + //------------------------------------------------------------------------------- + // LTI Extensions + + // Add grading preferences fieldset where the tool is allowed to return grades + $mform->addElement('select', 'lti_acceptgrades', get_string('accept_grades_admin', 'lti'), $options); + $mform->setDefault('lti_acceptgrades', '2'); + $mform->addHelpButton('lti_acceptgrades', 'accept_grades_admin', 'lti'); + + // Add grading preferences fieldset where the tool is allowed to retrieve rosters + $mform->addElement('select', 'lti_allowroster', get_string('share_roster_admin', 'lti'), $options); + $mform->setDefault('lti_allowroster', '2'); + $mform->addHelpButton('lti_allowroster', 'share_roster_admin', 'lti'); + + $mform->addElement('checkbox', 'lti_forcessl', ' ', ' ' . get_string('force_ssl', 'lti'), $options); + $mform->setDefault('lti_forcessl', '0'); + $mform->addHelpButton('lti_forcessl', 'force_ssl', 'lti'); + + if (!empty($this->_customdata->isadmin)) { + //------------------------------------------------------------------------------- + // Add setup parameters fieldset + $mform->addElement('header', 'setupoptions', get_string('miscellaneous', 'lti')); + + // Adding option to change id that is placed in context_id + $idoptions = array(); + $idoptions[0] = get_string('id', 'lti'); + $idoptions[1] = get_string('courseid', 'lti'); + + $mform->addElement('text', 'lti_organizationid', get_string('organizationid', 'lti')); + $mform->setType('lti_organizationid', PARAM_TEXT); + $mform->addHelpButton('lti_organizationid', 'organizationid', 'lti'); + + $mform->addElement('text', 'lti_organizationurl', get_string('organizationurl', 'lti')); + $mform->setType('lti_organizationurl', PARAM_TEXT); + $mform->addHelpButton('lti_organizationurl', 'organizationurl', 'lti'); + } + + /* Suppress this for now - Chuck + $mform->addElement('text', 'lti_organizationdescr', get_string('organizationdescr', 'lti')); + $mform->setType('lti_organizationdescr', PARAM_TEXT); + $mform->addHelpButton('lti_organizationdescr', 'organizationdescr', 'lti'); + */ + + //------------------------------------------------------------------------------- + // Add a hidden element to signal a tool fixing operation after a problematic backup - restore process + //$mform->addElement('hidden', 'lti_fix'); + + $tab = optional_param('tab', '', PARAM_ALPHAEXT); + $mform->addElement('hidden', 'tab', $tab); + + $courseid = optional_param('course', 1, PARAM_INT); + $mform->addElement('hidden', 'course', $courseid); + + //------------------------------------------------------------------------------- + // Add standard buttons, common to all modules + $this->add_action_buttons(); + + } +} diff --git a/mod/lti/grade.php b/mod/lti/grade.php new file mode 100644 index 00000000000..4e38cee54e8 --- /dev/null +++ b/mod/lti/grade.php @@ -0,0 +1,167 @@ +. +// +// 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 submissions-specific code for the lti module + * + * @package mod + * @subpackage 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 + */ + +require_once("../../config.php"); +require_once($CFG->dirroot.'/mod/lti/lib.php'); +require_once($CFG->libdir.'/plagiarismlib.php'); + +$id = optional_param('id', 0, PARAM_INT); // Course module ID +$l = optional_param('l', 0, PARAM_INT); // lti instance ID +$mode = optional_param('mode', 'all', PARAM_ALPHA); // What mode are we in? +$download = optional_param('download' , 'none', PARAM_ALPHA); //ZIP download asked for? + +if ($l) { // Two ways to specify the module + $lti = $DB->get_record('lti', array('id' => $l), '*', MUST_EXIST); + $cm = get_coursemodule_from_instance('lti', $lti->id, $lti->course, false, MUST_EXIST); + +} else { + $cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST); + $lti = $DB->get_record('lti', array('id' => $cm->instance), '*', MUST_EXIST); +} + +$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + +require_login($course, false, $cm); +$context = get_context_instance(CONTEXT_MODULE, $cm->id); +require_capability('mod/lti:grade', $context); + +$url = new moodle_url('/mod/lti/grade.php', array('id' => $cm->id)); +if ($mode !== 'all') { + $url->param('mode', $mode); +} +$PAGE->set_url($url); + +$module = array( + 'name' => 'mod_lti_submissions', + 'fullpath' => '/mod/lti/submissions.js', + 'requires' => array('base'), + 'strings' => array(), +); + +$PAGE->requires->js_init_call('M.mod_lti.submissions.init', array(), true, $module); + +$PAGE->requires->yui2_lib('datatable'); + +$submissionquery = ' + SELECT s.id, u.firstname, u.lastname, u.id AS userid, s.datesubmitted, s.gradepercent + FROM {lti_submission} s + INNER JOIN {user} u ON s.userid = u.id + WHERE s.ltiid = :ltiid + ORDER BY s.datesubmitted DESC +'; + +$submissions = $DB->get_records_sql($submissionquery, array('ltiid' => $lti->id)); + +$html = ' + + + +'; + +$rowtemplate = ' + + + + + + + + + + + +'; + +$rows = ''; + +foreach ($submissions as $submission) { + $row = $rowtemplate; + + foreach ($submission as $key => $value) { + if ($key === 'datesubmitted') { + $value = userdate($value); + } + + $row = str_replace('', $value, $row); + } + + $rows .= $row; +} + +$table = str_replace('', $rows, $html); + +$title = 'Submissions for ' . $lti->name; + +$PAGE->set_title(format_string($title , true)); +$PAGE->set_heading($course->fullname); + +echo $OUTPUT->header(); +echo $OUTPUT->heading($title ); + +echo $table; + +echo $OUTPUT->footer(); diff --git a/mod/lti/index.php b/mod/lti/index.php new file mode 100644 index 00000000000..5c2dff4fe19 --- /dev/null +++ b/mod/lti/index.php @@ -0,0 +1,116 @@ +. +// +// 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 page lists all the instances of lti in a particular course + * + * @package mod + * @subpackage 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 + */ +require_once("../../config.php"); +require_once($CFG->dirroot.'/mod/lti/lib.php'); + +$id = required_param('id', PARAM_INT); // course id + +$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST); + +require_login($course); +$PAGE->set_pagelayout('incourse'); + +add_to_log($course->id, "lti", "view all", "index.php?id=$course->id", ""); + +$PAGE->set_url('/mod/lti/index.php', array('id' => $course->id)); +$pagetitle = strip_tags($course->shortname.': '.get_string("modulenamepluralformatted", "lti")); +$PAGE->set_title($pagetitle); +$PAGE->set_heading($course->fullname); + +echo $OUTPUT->header(); + +// Print the main part of the page +echo $OUTPUT->heading(get_string("modulenamepluralformatted", "lti")); + +// Get all the appropriate data +if (! $basicltis = get_all_instances_in_course("lti", $course)) { + notice(get_string('noltis', 'lti'), "../../course/view.php?id=$course->id"); + die; +} + +// Print the list of instances (your module will probably extend this) +$timenow = time(); +$strname = get_string("name"); +$strsectionname = get_string('sectionname', 'format_'.$course->format); +$usesections = course_format_uses_sections($course->format); +if ($usesections) { + $sections = get_all_sections($course->id); +} + +$table = new html_table(); +$table->attributes['class'] = 'generaltable mod_index'; + +if ($usesections) { + $table->head = array ($strsectionname, $strname); + $table->align = array ("center", "left"); +} else { + $table->head = array ($strname); +} + +foreach ($basicltis as $basiclti) { + if (!$basiclti->visible) { + //Show dimmed if the mod is hidden + $link = "coursemodule\">$basiclti->name"; + } else { + //Show normal if the mod is visible + $link = "coursemodule\">$basiclti->name"; + } + + if ($course->format == "weeks" or $course->format == "topics") { + $table->data[] = array ($basiclti->section, $link); + } else { + $table->data[] = array ($link); + } +} + +echo "
"; + +echo html_writer::table($table); + +// Finish the page +echo $OUTPUT->footer(); diff --git a/mod/lti/instructor_edit_tool_type.php b/mod/lti/instructor_edit_tool_type.php new file mode 100644 index 00000000000..36c56b5de79 --- /dev/null +++ b/mod/lti/instructor_edit_tool_type.php @@ -0,0 +1,131 @@ +. + +/** + * MRTODO: Brief description of this file + * + * @package mod + * @subpackage lti + * @copyright 2011 onwards MRTODO + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once('../../config.php'); +require_once($CFG->dirroot.'/mod/lti/edit_form.php'); + +$courseid = required_param('course', PARAM_INT); + +require_login($courseid, false); +$url = new moodle_url('/mod/lti/instructor_edit_tool_type.php'); +$PAGE->set_url($url); +$PAGE->set_pagelayout('popup'); + +$action = optional_param('action', null, PARAM_TEXT); +$typeid = optional_param('typeid', null, PARAM_INT); + +require_capability('mod/lti:addcoursetool', get_context_instance(CONTEXT_COURSE, $courseid)); + +if (!empty($typeid)) { + $type = lti_get_type($typeid); + if ($type->course != $courseid) { + throw new Exception('You do not have permissions to edit this tool type.'); + die; + } +} + +$data = data_submitted(); + +if (isset($data->submitbutton) && confirm_sesskey()) { + $type = new stdClass(); + + if (!empty($typeid)) { + $type->id = $typeid; + $name = json_encode($data->lti_typename); + + lti_update_type($type, $data); + + $fromdb = lti_get_type($typeid); + $json = json_encode($fromdb); + + //Output script to update the calling window. + $script = " + + + + "; + + echo $script; + die; + } else { + $type->state = LTI_TOOL_STATE_CONFIGURED; + $type->course = $COURSE->id; + + $id = lti_add_type($type, $data); + + $fromdb = lti_get_type($id); + $json = json_encode($fromdb); + + //Output script to update the calling window. + $script = " + + + + "; + + echo $script; + + die; + } +} else if (isset($data->cancel)) { + $script = " + + + + "; + + echo $script; + die; +} + +//Delete action is called via ajax +if ($action == 'delete') { + lti_delete_type($typeid); + die; +} + +echo $OUTPUT->header(); + +echo $OUTPUT->heading(get_string('toolsetup', 'lti')); + +if ($action == 'add') { + $form = new mod_lti_edit_types_form(); + $form->display(); +} else if ($action == 'edit') { + $form = new mod_lti_edit_types_form(); + $type = lti_get_type_type_config($typeid); + $form->set_data($type); + $form->display(); +} + +echo $OUTPUT->footer(); diff --git a/mod/lti/lang/en/lti.php b/mod/lti/lang/en/lti.php new file mode 100644 index 00000000000..f37d160d588 --- /dev/null +++ b/mod/lti/lang/en/lti.php @@ -0,0 +1,628 @@ +. +// +// 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 en_utf8 translation of the Basic LTI module + * + * @package mod + * @subpackage 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; + +//Permissions +$string['lti:view'] = 'View LTI activities'; +$string['lti:grade'] = 'Grade LTI activities'; +$string['lti:addcoursetool'] = 'Grade LTI activities'; +$string['lti:requesttooladd'] = 'Submit a tool to admins for configuration'; + +$string['accept'] = 'Accept'; +$string['activity'] = 'Activity'; +$string['addnewapp'] = 'Enable External Application'; +$string['addserver'] = 'Add new trusted server'; +$string['addtype'] = 'Add external tool configuration'; +$string['allow'] = 'Allow'; +$string['allowinstructorcustom'] = 'Allow instructors to add custom parameters'; +$string['share_roster_admin'] = 'Tool may access course roster'; +$string['allowsetting'] = 'Allow tool to store 8K of settings in Moodle'; +$string['always'] = 'Always'; +$string['lti'] = 'LTI'; +$string['basiclti'] = 'LTI'; +$string['basiclti_base_string'] = 'LTI OAuth Base String'; +$string['basiclti_in_new_window'] = 'Your activity has opened in a new window'; +$string['basiclti_endpoint'] = 'LTI Launch Endpoint'; +$string['basiclti_parameters'] = 'LTI Launch Parameters'; +$string['basicltiactivities'] = 'LTI Activities'; +$string['basicltifieldset'] = 'Custom example fieldset'; +$string['basicltiintro'] = 'Activity Description'; +$string['basicltiname'] = 'Activity Name'; +$string['basicltisettings'] = 'Basic Learning Tool Interoperability Settings'; +$string['comment'] = 'Comment'; +$string['configpassword'] = 'Default Remote Tool Password'; +$string['configpreferheight'] = 'Default preferred height'; +$string['configpreferwidget'] = 'Set widget as default launch'; +$string['configpreferwidth'] = 'Default preferred width'; +$string['configresourceurl'] = 'Default Resource URL'; +$string['configtoolurl'] = 'Default Remote Tool URL'; +$string['configtypes'] = 'Enable LTI Applications'; +$string['courseid'] = 'Course id number'; +$string['coursemisconf'] = 'Course is misconfigured'; +$string['curllibrarymissing'] = 'PHP Curl library must be installed to use LTI'; +$string['custom'] = 'Custom parameters'; +$string['custominstr'] = 'Custom parameters'; +$string['debuglaunch'] = 'Debug Option'; +$string['debuglaunchoff'] = 'Normal launch'; +$string['debuglaunchon'] = 'Debug launch'; +$string['donot'] = 'Do not send'; +$string['donotaccept'] = 'Do not accept'; +$string['donotallow'] = 'Do not allow'; +$string['enableemailnotification'] = 'Send notification emails'; +$string['enableemailnotification_help'] = 'If enabled, students will receive email notification when their tool submissions are graded.'; +$string['errormisconfig'] = 'Misconfigured tool. Please ask your Moodle administrator to fix the configuration of the tool.'; +$string['extensions'] = 'LTI Extension Services'; +$string['failedtoconnect'] = 'Moodle was unable to communicate with the \"$a\" system'; +$string['filterconfig'] = 'LTI administration'; +$string['filtername'] = 'LTI'; +$string['filter_basiclti_configlink'] = 'Configure your preferred sites and their passwords'; +$string['filter_basiclti_password'] = 'Password is mandatory'; +$string['fixexistingconf'] = 'Use an existing configuration for the misconfigured instance'; +$string['fixnew'] = 'New Configuration'; +$string['fixnewconf'] = 'Define a new configuration for the misconfigured instance'; +$string['fixold'] = 'Use Existing'; +$string['grading'] = 'Grade Routing'; +$string['id'] = 'id'; +$string['imsroleadmin'] = 'Instructor,Administrator'; +$string['imsroleinstructor'] = 'Instructor'; +$string['imsrolelearner'] = 'Learner'; +$string['invalidid'] = 'LTI ID was incorrect'; +$string['launch_in_moodle'] = 'Launch tool in moodle'; +$string['launch_in_popup'] = 'Launch tool in a pop-up'; +$string['launchinpopup'] = 'Launch Container'; +$string['launchoptions'] = 'Launch Options'; +$string['lti_errormsg'] = 'The tool returned the following error message: \"$a\"'; +$string['misconfiguredtools'] = 'Misconfigured tool instances were detected'; +$string['missingparameterserror'] = 'The page is misconfigured: \"$a\"'; +$string['module_class_type'] = 'Moodle module type'; +$string['modulename'] = 'External Tool'; +$string['modulenameplural'] = 'basicltis'; +$string['modulenamepluralformatted'] = 'LTI Instances'; +$string['never'] = 'Never'; +$string['noattempts'] = 'No attempts have been made on this tool instance'; +$string['noltis'] = 'There are no lti instances'; +$string['noservers'] = 'No servers found'; +$string['notypes'] = 'There are currently no LTI tools setup in Moodle. Click the Install link above to add some.'; +$string['noviewusers'] = 'No users were found with permissions to use this tool'; +$string['optionalsettings'] = 'Optional settings'; +$string['organization'] ='Organization details'; +$string['organizationdescr'] ='Organization Description'; +$string['organizationid'] ='Organization ID'; +$string['organizationurl'] ='Organization URL'; +$string['pagesize'] = 'Submissions shown per page'; +$string['password'] = 'Shared Secret'; +$string['password_admin'] = 'Shared Secret'; +$string['pluginadministration'] = 'LTI administration'; +$string['pluginname'] = 'LTI'; +$string['preferheight'] = 'Preferred Height'; +$string['preferwidget'] = 'Prefer Widget Launch'; +$string['preferwidth'] = 'Preferred Width'; +$string['press_to_submit'] = 'Press to launch this activity'; +$string['privacy'] = 'Privacy'; +$string['quickgrade'] = 'Allow quick grading'; +$string['quickgrade_help'] = 'If enabled, multiple tools can be graded on one page. Add grades and comments then click the "Save all my feedback" button to save all changes for that page.'; +$string['redirect'] = 'You will be redirected in few seconds. If you are not, press the button.'; +$string['resource'] = 'Resource'; +$string['resourcekey'] = 'Consumer Key'; +$string['resourcekey_admin'] = 'Consumer Key'; +$string['resourceurl'] = 'Resource URL'; +$string['saveallfeedback'] = 'Save all my feedback'; +$string['send'] = 'Send'; +$string['share_email_admin'] = 'Share launcher\'s email with tool'; +$string['share_name_admin'] = 'Share launcher\'s name with tool'; +$string['setdefault'] = 'Set a default value for the professor if delegating'; +$string['setupbox'] = 'LTI Tool Setup Box'; +$string['setupoptions'] = 'Setup Options'; +$string['size'] = 'Size parameters'; +$string['submission'] = 'Submission'; +$string['toggle_debug_data'] = 'Toggle Debug Data'; +$string['toolsetup'] = 'External Tool Configuration'; +$string['toolurl'] = 'Tool Base URL'; +$string['typename'] = 'Tool Name'; +$string['types'] = 'Types'; +$string['validurl'] = 'A valid URL must start with http(s)://'; +$string['viewsubmissions'] = 'View submissions and grading screen'; + +//New admin strings +$string['show_in_course'] = 'Show tool type when creating tool instances'; +$string['delegate'] = 'Delegate to Instructor'; +$string['tool_settings'] = 'Tool Settings'; +$string['miscellaneous'] = 'Miscellaneous'; +$string['embed'] = 'Embed'; +$string['embed_no_blocks'] = 'Embed, without blocks'; +$string['new_window'] = 'New window'; +$string['default_launch_container'] = 'Default Launch Container'; +$string['active'] = 'Active'; +$string['pending'] = 'Pending'; +$string['rejected'] = 'Rejected'; +$string['baseurl'] = 'Base URL'; +$string['action'] = 'Action'; +$string['createdon'] = 'Created On'; +$string['accept'] = 'Accept'; +$string['update'] = 'Update'; +$string['delete'] = 'Delete'; +$string['reject'] = 'Reject'; +$string['external_tool_types'] = 'External Tool Types'; +$string['no_lti_configured'] = 'There are no active External Tools configured.'; +$string['no_lti_pending'] = 'There are no pending External Tools.'; +$string['no_lti_rejected'] = 'There are no rejected External Tools.'; +$string['accept_grades_admin'] = 'Accept grades from the tool'; +$string['force_ssl'] = 'Force SSL'; +$string['lti_administration'] = 'LTI Administration'; + +//New instructor strings +$string['display_name'] = 'Display activity name when launched'; +$string['display_description'] = 'Display activity description when launched'; +$string['external_tool_type'] = 'External tool type'; +$string['launch_url'] = 'Launch URL'; +$string['secure_launch_url'] = 'Secure Launch URL'; +$string['share_name'] = 'Share launcher\'s name with the tool'; +$string['share_email'] = 'Share launcher\'s email with the tool'; +$string['accept_grades'] = 'Accept grades from the tool'; +$string['share_roster'] = 'Allow the tool to access this course\'s roster'; +$string['automatic'] = 'Automatic, based on Launch URL'; +$string['default'] = 'Default'; + +$string['edittype'] = 'Edit external tool configuration'; +$string['deletetype'] = 'Delete external tool configuration'; +$string['delete_confirmation'] = 'Are you sure you want to delete this external tool configuration?'; +$string['cannot_edit'] = 'You may not edit this tool configuration.'; +$string['cannot_delete'] = 'You may not delete this tool configuration.'; +$string['global_tool_types'] = 'Global tool types'; +$string['course_tool_types'] = 'Course tool types'; + +$string['using_tool_configuration'] = 'Using tool configuration: '; +$string['domain_mismatch'] = 'Launch URL\'s domain does not match tool configuration.'; +$string['custom_config'] = 'Using custom tool configuration.'; +$string['tool_config_not_found'] = 'Tool configuration not found for this URL.'; + +$string['icon_url'] = 'Icon URL'; +$string['secure_icon_url'] = 'Secure Icon URL'; + +$string['return_to_course'] = 'Click here to return to the course.'; + +$string['lti_launch_error'] = 'An error occured when launching the external tool: '; +$string['lti_launch_error_unsigned_help'] = ' +

+ This error may be a result of a missing consumer key and shared secret for the tool provider. +

+

+ If you have a consumer key and shared secret, you may enter it when editing the external tool instance (make sure advanced options are visible).
+ Alternatively, you may create a course level tool provider configuration here. +

+'; + +$string['lti_launch_error_tool_request'] = ' +

+ To submit a request for an administrator to complete the tool configuration, click here. +

+'; + +$string['lti_tool_request_added'] = ' + Tool configuration request successfully submitted. You may need to contact an administrator to complete the tool configuration. +'; + +$string['lti_tool_request_existing'] = ' + A tool configuration for the tool domain has already been submitted. +'; + +//Instance help + +$string['external_tool_type_help'] = ' +The main purpose of a tool configuration is to set up a secure communication channel between Moodle and the tool provider. +It also provides an opportunity for configuration defaults and setting up additional services provided by the tool. + + + +Tool type editing:
+ +Three icons are available after the External tool type dropdown list: + + +'; + +$string['launch_url_help'] = ' +The Launch URL indicates the web address of the External Tool, and may contain additional information, such as the resource to show. +If you are unsure what to enter for the Launch URL, please check with the tool provider for more information. + +If you have selected a specific tool type, you may not need to enter a Launch URL. If the tool link is used to just launch +into the tool provider\'s system, and not go to a specific resource, this will likely be the case. +'; + +$string['secure_launch_url_help'] = ' +Similar to Launch URL, but used instead of the launch url if high security is required. Moodle will use the +secure launch URL instead of the launch URL if the Moodle site is accessed through SSL, or if the tool configuration +is set to always launch through SSL. + +The Launch URL may also be set to an https address to force launching through SSL, and this field may be left blank. +'; + +$string['icon_url_help'] = ' +The icon URL allows the icon that shows up in the course listing for this activity to be modified. Instead of using the default +LTI icon, an icon which conveys the type of activity may be specified. +'; + +$string['secure_icon_url_help'] = ' +Similar to the icon URL, but used if the user accessing Moodle securely through SSL. The main purpose for this field is to prevent +the browser from warning the user if the underlying page was accessed over SSL, but requesting to show an unsecure image. +'; + +$string['launchinpopup_help'] = ' +The launch container affects the display of the tool when launched from the course. Some launch containers provide more screen +real estate to the tool, and others provide a more integrated feel with the Moodle environemnt. + + +'; + +$string['resourcekey_help'] = ' +For pre-configured tools, it is not necessary to enter a resource key here, as the consumer key will be +provided as part of the configuration process. + +This field should be entered if creating a link to a tool provider which is not already configured. +If the tool provider is to be used more than once in this course, adding a course tool configuration is a good idea. + +The consumer key can be thought of as a username used to authenticate access to the tool. +It can be used by the tool provider to uniquely identify the Moodle site from which users launch into the tool. + +The consumer key must be provided by the tool provider. The method of obtaining a consumer key varies between +tool providers. It may be an automated process, or it may require a dialogue with the tool provider. + +Tools which do not require secure communication from Moodle and do not provide additional services (such as grade reporting) +may not require a resource key. +'; + +$string['password_help'] = ' +For pre-configured tools, it is not necessary to enter a shared secret here, as the shared secret will be +provided as part of the configuration process. + +This field should be entered if creating a link to a tool provider which is not already configured. +If the tool provider is to be used more than once in this course, adding a course tool configuration is a good idea. + +The shared secret can be thought of as a password used to authenticate access to the tool. It should be provided +along with the consumer key from the tool provider. + +Tools which do not require secure communication from Moodle and do not provide additional services (such as grade reporting) +may not require a shared secret. +'; + +$string['custom_help'] = ' +Custom parameters are settings used by the tool provider. For example, a custom parameter may be used to display +a specific resource from the provider. + +It is safe to leave this field unchanged unless directed by the tool provider. +'; + +$string['share_name_help'] = ' +Specify whether the full name of the user launching the tool should be shared with the tool provider. +The tool provider may need launchers\' names to show meaningful information within the tool. + +Note that this setting may be overriden in the tool configuration. +'; + +$string['share_email_help'] = ' +Specify whether the e-mail address of the user launching the tool will be shared with the tool provider. +The tool provider may need launcher\'s e-mail addresses to distinguish users with the same name, or send e-mails +to users based on actions within the tool. + +Note that this setting may be overriden in the tool configuration. +'; + +$string['accept_grades_help'] = ' +Specify whether the tool provider can add, update, read, and delete grades associated only with this external tool instance. + +Some tool providers support reporting grades back to Moodle based on actions taken within the tool, creating a more integrated +experience. + +Note that this setting may be overriden in the tool configuration. +'; + +$string['share_roster_help'] = ' +Specify whether the tool can access the list of users enrolled in this course. + +Note that this setting may be overriden in the tool configuration. +'; + +$string['display_name_help'] = ' +If selected, the activity name (specified above) will display above the tool provider\'s content. + +It is possible that the tool provider may also display the title. This option can prevent the activity title from +being displayed twice. + +The title is never displayed when the tool\'s launch container is in a new window. +'; + +$string['display_description_help'] = ' +If selected, the activity description (specified above) will display above the tool provider\'s content. + +The description may be used to provide additional instructions for launchers of the tool, but it is not required. + +The description is never displayed when the tool\'s launch container is in a new window. +'; + +//Admin help +$string['typename_help'] = ' +The tool name is used to identify the tool provider within Moodle. The name entered will be visible +to instructors when adding external tools within courses. +'; + +$string['toolurl_help'] = ' +The tool base URL is used to match tool launch URLs to the correct tool configuration. Prefxing the URL with http(s) is optional. + +Additionally, the base URL is used as the launch URL if a launch URL is not specified in the external tool instance. + + + + + + + + + + + + + + + + + + + + + + +
+ Base URL + + Matches +
+ tool.com + + tool.com, tool.com/quizzes, tool.com/quizzes/quiz.php?id=10, www.tool.com/quizzes +
+ www.tool.com/quizzes + + tool.com/quizzes, tool.com/quizzes/take.php?id=10, www.tool.com/quizzes +
+ quiz.tool.com + + quiz.tool.com, quiz.tool.com/take.php?id=10 +
+ +If two different tool configurations are for the same domain, the most specific match will be used. +'; + +$string['resourcekey_admin_help'] = ' +The consumer key can be thought of as a username used to authenticate access to the tool. +It can be used by the tool provider to uniquely identify the Moodle site from which users launch into the tool. + +The consumer key must be provided by the tool provider. The method of obtaining a consumer key varies between +tool providers. It may be an automated process, or it may require a dialogue with the tool provider. + +Tools which do not require secure communication from Moodle and do not provide additional services (such as grade reporting) +may not require a resource key. +'; + +$string['password_admin_help'] = ' +The shared secret can be thought of as a password used to authenticate access to the tool. It should be provided +along with the consumer key from the tool provider. + +Tools which do not require secure communication from Moodle and do not provide additional services (such as grade reporting) +may not require a shared secret. +'; + +$string['show_in_course_help'] = ' +If selected, this tool configuration will appear in the "External tool type" dropdown when instructors +configure external tools within courses. + +In most cases, this option does not need to be selected. Instructors can use this tool configuration +based on the Launch URL matching the Tool base URL, which is the preferred method. + +The only case in which this option should be selected is if the tool configuration is just intended for single sign on. +For example, if all launches to the tool provider just take the user to a landing page instead of to a specific resource. +'; + +$string['default_launch_container_help'] = ' +The launch container affects the display of the tool when launched from the course. Some launch containers provide more screen +real estate to the tool, and others provide a more integrated feel with the Moodle environemnt. + + +'; + +$string['share_name_admin_help'] = ' +Specify whether the full name of the user launching the tool should be shared with the tool provider. +The tool provider may need launchers\' names to show meaningful information within the tool. +'; + +$string['share_email_admin_help'] = ' +Specify whether the e-mail address of the user launching the tool will be shared with the tool provider. +The tool provider may need launcher\'s e-mail addresses to distinguish users with the same name in the UI, or send e-mails +to users based on actions within the tool. +'; + +$string['accept_grades_admin_help'] = ' +Specify whether the tool provider can add, update, read, and delete grades associated with instances of this tool type. + +Some tool providers support reporting grades back to Moodle based on actions taken within the tool, creating a more integrated +experience. +'; + +$string['share_roster_admin_help'] = ' +Specify whether the tool can access the list of users enrolled in courses from which this tool type is launched. +'; + +$string['main_admin'] = 'General help'; + +$string['main_admin_help'] = ' +External tools allow Moodle users to seamlessly interact with learning resources hosted remotely. Through a special +launch protocol, the remote tool will have access to some general information about the launching user. For example, +the institution name, course id, user id, and other information such as the user\'s name or e-mail address. + +Tool types listed on this page are separated into three categories: + + +'; + +$string['modulename_help'] = ' +External tools allow Moodle users to interact with learning resources and activities on other web sites. For instance, an +external tool could provide access to a new activity type or learning materials from a publisher. + +To setup an external tool instance a tool provider which supports LTI (Learning Tools Interoperability) is required. +If you find a tool provider which supports LTI, they should be able to provide instructions on how to configure the +external tool instance. Additionally, tool types configured by a site administrator will also be available for use. + +External tools differ from URL resources in a few ways: + +'; + +$string['force_ssl_help'] = ' +Selecting this option forces all launches to this tool provider to use SSL. + +In addition, all web service requests from the tool provider will use SSL. + +If using this option, confirm that this Moodle site and the tool provider support SSL. +'; + +$string['organizationid_help'] = ' +A unique identifier for this Moodle instance. Typically, the DNS name of the organization is used. + +If this field is left blank, the host name of this Moodle site will be used as the default value. +'; + +$string['organizationurl_help'] = ' +The base URL of this Moodle instance. + +If this field is left blank, a default value will be used based on the site configuration. +'; diff --git a/mod/lti/launch.php b/mod/lti/launch.php new file mode 100644 index 00000000000..3afd2e35c3b --- /dev/null +++ b/mod/lti/launch.php @@ -0,0 +1,65 @@ +. +// +// 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 necessary code to view a lti activity instance + * + * @package mod + * @subpackage 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 + */ + +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); // Course Module ID + +$cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST); +$lti = $DB->get_record('lti', array('id' => $cm->instance), '*', MUST_EXIST); +$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + +require_login($course); + +add_to_log($course->id, "lti", "launch", "launch.php?id=$cm->id", "$lti->id"); + +$lti->cmid = $cm->id; +lti_view($lti); + diff --git a/mod/lti/lib.php b/mod/lti/lib.php new file mode 100644 index 00000000000..58531628792 --- /dev/null +++ b/mod/lti/lib.php @@ -0,0 +1,411 @@ +. +// +// 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 library of functions and constants for the lti module + * + * @package mod + * @subpackage 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; + +/** + * List of features supported in URL module + * @param string $feature FEATURE_xx constant for requested feature + * @return mixed True if module supports feature, false if not, null if doesn't know + */ +function lti_supports($feature) { + switch($feature) { + case FEATURE_GROUPS: return false; + case FEATURE_GROUPINGS: return false; + case FEATURE_GROUPMEMBERSONLY: return true; + case FEATURE_MOD_INTRO: return true; + case FEATURE_COMPLETION_TRACKS_VIEWS: return true; + case FEATURE_GRADE_HAS_GRADE: return true; + case FEATURE_GRADE_OUTCOMES: return true; + case FEATURE_BACKUP_MOODLE2: return true; + + default: return null; + } +} + +/** + * Given an object containing all the necessary data, + * (defined by the form in mod.html) this function + * will create a new instance and return the id number + * of the new instance. + * + * @param object $instance An object from the form in mod.html + * @return int The id of the newly inserted basiclti record + **/ +function lti_add_instance($lti, $mform) { + global $DB, $CFG; + require_once($CFG->dirroot.'/mod/lti/locallib.php'); + + $lti->timecreated = time(); + $lti->timemodified = $lti->timecreated; + $lti->servicesalt = uniqid('', true); + + if (!isset($lti->grade)) { + $lti->grade = 100; // TODO: Why is this harcoded here and default @ DB + } + + $lti->id = $DB->insert_record('lti', $lti); + + if ($lti->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS) { + if (!isset($lti->cmidnumber)) { + $lti->cmidnumber = ''; + } + + lti_grade_item_update($lti); + } + + return $lti->id; +} + +/** + * Given an object containing all the necessary data, + * (defined by the form in mod.html) this function + * will update an existing instance with new data. + * + * @param object $instance An object from the form in mod.html + * @return boolean Success/Fail + **/ +function lti_update_instance($lti, $mform) { + global $DB, $CFG; + require_once($CFG->dirroot.'/mod/lti/locallib.php'); + + $lti->timemodified = time(); + $lti->id = $lti->instance; + + if (!isset($lti->showtitle)) { + $lti->showtitle = 0; + } + + if (!isset($lti->showdescription)) { + $lti->showdescription = 0; + } + + if (!isset($lti->grade)) { + $lti->grade = $DB->get_field('lti', 'grade', array('id' => $lti->id)); + } + + if ($lti->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS) { + lti_grade_item_update($lti); + } else { + lti_grade_item_delete($lti); + } + + return $DB->update_record('lti', $lti); +} + +/** + * Given an ID of an instance of this module, + * this function will permanently delete the instance + * and any data that depends on it. + * + * @param int $id Id of the module instance + * @return boolean Success/Failure + **/ +function lti_delete_instance($id) { + global $DB; + + if (! $basiclti = $DB->get_record("lti", array("id" => $id))) { + return false; + } + + $result = true; + + # Delete any dependent records here # + lti_grade_item_delete($basiclti); + + return $DB->delete_records("lti", array("id" => $basiclti->id)); +} + +/** + * Given a coursemodule object, this function returns the extra + * information needed to print this activity in various places. + * For this module we just need to support external urls as + * activity icons + * + * @param cm_info $coursemodule + * @return cached_cm_info info + */ +function lti_get_coursemodule_info($coursemodule) { + global $DB, $CFG; + require_once($CFG->dirroot.'/mod/lti/locallib.php'); + + if (!$lti = $DB->get_record('lti', array('id' => $coursemodule->instance), + 'icon, secureicon')) { + return null; + } + + $info = new cached_cm_info(); + + // We want to use the right icon based on whether the + // current page is being requested over http or https. + if (lti_request_is_using_ssl() && !empty($lti->secureicon)) { + $info->iconurl = new moodle_url($lti->secureicon); + } else if (!empty($lti->icon)) { + $info->iconurl = new moodle_url($lti->icon); + } + + return $info; +} + +/** + * Return a small object with summary information about what a + * user has done with a given particular instance of this module + * Used for user activity reports. + * $return->time = the time they did it + * $return->info = a short text description + * + * @return null + * @TODO: implement this moodle function (if needed) + **/ +function lti_user_outline($course, $user, $mod, $basiclti) { + return null; +} + +/** + * Print a detailed representation of what a user has done with + * a given particular instance of this module, for user activity reports. + * + * @return boolean + * @TODO: implement this moodle function (if needed) + **/ +function lti_user_complete($course, $user, $mod, $basiclti) { + return true; +} + +/** + * Given a course and a time, this module should find recent activity + * that has occurred in basiclti activities and print it out. + * Return true if there was output, or false is there was none. + * + * @uses $CFG + * @return boolean + * @TODO: implement this moodle function + **/ +function lti_print_recent_activity($course, $isteacher, $timestart) { + return false; // True if anything was printed, otherwise false +} + +/** + * Function to be run periodically according to the moodle cron + * This function searches for things that need to be done, such + * as sending out mail, toggling flags etc ... + * + * @uses $CFG + * @return boolean + **/ +function lti_cron () { + return true; +} + +/** + * Must return an array of grades for a given instance of this module, + * indexed by user. It also returns a maximum allowed grade. + * + * Example: + * $return->grades = array of grades; + * $return->maxgrade = maximum allowed grade; + * + * return $return; + * + * @param int $basicltiid ID of an instance of this module + * @return mixed Null or object with an array of grades and with the maximum grade + * + * @TODO: implement this moodle function (if needed) + **/ +function lti_grades($basicltiid) { + return null; +} + +/** + * Must return an array of user records (all data) who are participants + * for a given instance of basiclti. Must include every user involved + * in the instance, independient of his role (student, teacher, admin...) + * See other modules as example. + * + * @param int $basicltiid ID of an instance of this module + * @return mixed boolean/array of students + * + * @TODO: implement this moodle function + **/ +function lti_get_participants($basicltiid) { + return false; +} + +/** + * This function returns if a scale is being used by one basiclti + * it it has support for grading and scales. Commented code should be + * modified if necessary. See forum, glossary or journal modules + * as reference. + * + * @param int $basicltiid ID of an instance of this module + * @return mixed + * + * @TODO: implement this moodle function (if needed) + **/ +function lti_scale_used ($basicltiid, $scaleid) { + $return = false; + + //$rec = get_record("basiclti","id","$basicltiid","scale","-$scaleid"); + // + //if (!empty($rec) && !empty($scaleid)) { + // $return = true; + //} + + return $return; +} + +/** + * Checks if scale is being used by any instance of basiclti. + * This function was added in 1.9 + * + * This is used to find out if scale used anywhere + * @param $scaleid int + * @return boolean True if the scale is used by any basiclti + * + */ +function lti_scale_used_anywhere($scaleid) { + global $DB; + + if ($scaleid and $DB->record_exists('lti', array('grade' => -$scaleid))) { + return true; + } else { + return false; + } +} + +/** + * Execute post-install custom actions for the module + * This function was added in 1.9 + * + * @return boolean true if success, false on error + */ +function lti_install() { + return true; +} + +/** + * Execute post-uninstall custom actions for the module + * This function was added in 1.9 + * + * @return boolean true if success, false on error + */ +function lti_uninstall() { + return true; +} + +/** + * Returns available Basic LTI types + * + * @return array of basicLTI types + */ +function lti_get_lti_types() { + global $DB; + + return $DB->get_records('lti_types'); +} + +/** + * Create grade item for given basiclti + * + * @param object $basiclti object with extra cmidnumber + * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook + * @return int 0 if ok, error code otherwise + */ +function lti_grade_item_update($basiclti, $grades=null) { + global $CFG; + require_once($CFG->libdir.'/gradelib.php'); + + $params = array('itemname'=>$basiclti->name, 'idnumber'=>$basiclti->cmidnumber); + + if ($basiclti->grade > 0) { + $params['gradetype'] = GRADE_TYPE_VALUE; + $params['grademax'] = $basiclti->grade; + $params['grademin'] = 0; + + } else if ($basiclti->grade < 0) { + $params['gradetype'] = GRADE_TYPE_SCALE; + $params['scaleid'] = -$basiclti->grade; + + } else { + $params['gradetype'] = GRADE_TYPE_TEXT; // allow text comments only + } + + if ($grades === 'reset') { + $params['reset'] = true; + $grades = null; + } + + return grade_update('mod/lti', $basiclti->course, 'mod', 'lti', $basiclti->id, 0, $grades, $params); +} + +/** + * Delete grade item for given basiclti + * + * @param object $basiclti object + * @return object basiclti + */ +function lti_grade_item_delete($basiclti) { + global $CFG; + require_once($CFG->libdir.'/gradelib.php'); + + return grade_update('mod/lti', $basiclti->course, 'mod', 'lti', $basiclti->id, 0, null, array('deleted'=>1)); +} + +function lti_extend_settings_navigation($settings, $parentnode) { + global $PAGE; + + if (has_capability('mod/lti:grade', get_context_instance(CONTEXT_MODULE, $PAGE->cm->id))) { + $keys = $parentnode->get_children_key_list(); + + $node = navigation_node::create('Submissions', + new moodle_url('/mod/lti/grade.php', array('id'=>$PAGE->cm->id)), + navigation_node::TYPE_SETTING, null, 'mod_lti_submissions'); + + $parentnode->add_node($node, $keys[1]); + } +} diff --git a/mod/lti/localadminlib.php b/mod/lti/localadminlib.php new file mode 100644 index 00000000000..128c345b856 --- /dev/null +++ b/mod/lti/localadminlib.php @@ -0,0 +1,86 @@ +. +// +// 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 some functions and classes used by the lti + * module administration + * + * @package mod + * @subpackage 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->libdir.'/adminlib.php'); + +/** + * + * @TODO: finish doc this class and it's functions + */ +class admin_setting_ltimodule_configlink extends admin_setting { + + /** + * Constructor + * @param string $name of setting + * @param string $visiblename localised + * @param string $description long localised info + */ + public function __construct($name, $visiblename, $description) { + parent::__construct($name, $visiblename, $description, ''); + } + + public function get_setting() { + return true; + } + + public function write_setting($data) { + return ""; + } + + public function output_html($data, $query='') { + global $CFG; + return format_admin_setting($this, "", + '
'. + ''.get_string('filterconfig', 'lti').''. + '
', + $this->description, true, '', null, $query); + } +} diff --git a/mod/lti/locallib.php b/mod/lti/locallib.php new file mode 100644 index 00000000000..df870bdcd1d --- /dev/null +++ b/mod/lti/locallib.php @@ -0,0 +1,1185 @@ +. +// +// This file is part of BasicLTI4Moodle +// +// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability) +// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web +// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI +// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS +// are already supporting or going to support BasicLTI. This project Implements the consumer +// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas. +// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem +// at the GESSI research group at UPC. +// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI +// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a +// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier. +// +// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis +// of the Universitat Politecnica de Catalunya http://www.upc.edu +// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu + +/** + * This file contains the library of functions and constants for the lti module + * + * @package mod + * @subpackage 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; + +// TODO: Switch to core oauthlib once implemented - MDL-30149 +use moodle\mod\lti as lti; + +require_once($CFG->dirroot.'/mod/lti/OAuth.php'); + +define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i'); + +define('LTI_LAUNCH_CONTAINER_DEFAULT', 1); +define('LTI_LAUNCH_CONTAINER_EMBED', 2); +define('LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS', 3); +define('LTI_LAUNCH_CONTAINER_WINDOW', 4); +define('LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW', 5); + +define('LTI_TOOL_STATE_ANY', 0); +define('LTI_TOOL_STATE_CONFIGURED', 1); +define('LTI_TOOL_STATE_PENDING', 2); +define('LTI_TOOL_STATE_REJECTED', 3); + +define('LTI_SETTING_NEVER', 0); +define('LTI_SETTING_ALWAYS', 1); +define('LTI_SETTING_DELEGATE', 2); + +/** + * Prints a Basic LTI activity + * + * $param int $basicltiid Basic LTI activity id + */ +function lti_view($instance) { + global $PAGE, $CFG; + + if (empty($instance->typeid)) { + $tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course); + if ($tool) { + $typeid = $tool->id; + } else { + $typeid = null; + } + } else { + $typeid = $instance->typeid; + } + + if ($typeid) { + $typeconfig = lti_get_type_config($typeid); + } else { + //There is no admin configuration for this tool. Use configuration in the lti instance record plus some defaults. + $typeconfig = (array)$instance; + + $typeconfig['sendname'] = $instance->instructorchoicesendname; + $typeconfig['sendemailaddr'] = $instance->instructorchoicesendemailaddr; + $typeconfig['customparameters'] = $instance->instructorcustomparameters; + $typeconfig['acceptgrades'] = $instance->instructorchoiceacceptgrades; + $typeconfig['allowroster'] = $instance->instructorchoiceallowroster; + $typeconfig['forcessl'] = '0'; + } + + //Default the organizationid if not specified + if (empty($typeconfig['organizationid'])) { + $urlparts = parse_url($CFG->wwwroot); + + $typeconfig['organizationid'] = $urlparts['host']; + } + + if (!empty($instance->resourcekey)) { + $key = $instance->resourcekey; + } else if (!empty($typeconfig['resourcekey'])) { + $key = $typeconfig['resourcekey']; + } else { + $key = ''; + } + + if (!empty($instance->password)) { + $secret = $instance->password; + } else if (!empty($typeconfig['password'])) { + $secret = $typeconfig['password']; + } else { + $secret = ''; + } + + $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $typeconfig['toolurl']; + $endpoint = trim($endpoint); + + //If the current request is using SSL and a secure tool URL is specified, use it + if (lti_request_is_using_ssl() && !empty($instance->securetoolurl)) { + $endpoint = trim($instance->securetoolurl); + } + + //If SSL is forced, use the secure tool url if specified. Otherwise, make sure https is on the normal launch URL. + if ($typeconfig['forcessl'] == '1') { + if (!empty($instance->securetoolurl)) { + $endpoint = trim($instance->securetoolurl); + } + + $endpoint = lti_ensure_url_is_https($endpoint); + } else { + if (!strstr($endpoint, '://')) { + $endpoint = 'http://' . $endpoint; + } + } + + $orgid = $typeconfig['organizationid']; + + $course = $PAGE->course; + $requestparams = lti_build_request($instance, $typeconfig, $course); + + $launchcontainer = lti_get_launch_container($instance, $typeconfig); + $returnurlparams = array('course' => $course->id, 'launch_container' => $launchcontainer, 'instanceid' => $instance->id); + + if ( $orgid ) { + $requestparams["tool_consumer_instance_guid"] = $orgid; + } + + if (empty($key) || empty($secret)) { + $returnurlparams['unsigned'] = '1'; + + //Add the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns + $url = new moodle_url('/mod/lti/return.php', $returnurlparams); + $returnurl = $url->out(false); + + if ($typeconfig['forcessl'] == '1') { + $returnurl = lti_ensure_url_is_https($returnurl); + } + + $requestparams['launch_presentation_return_url'] = $returnurl; + } + + if (!empty($key) && !empty($secret)) { + $parms = lti_sign_parameters($requestparams, $endpoint, "POST", $key, $secret); + } else { + //If no key and secret, do the launch unsigned. + $parms = $requestparams; + } + + $debuglaunch = ( $instance->debuglaunch == 1 ); + + $content = lti_post_launch_html($parms, $endpoint, $debuglaunch); + + echo $content; +} + +function lti_build_sourcedid($instanceid, $userid, $launchid = null, $servicesalt) { + $data = new stdClass(); + + $data->instanceid = $instanceid; + $data->userid = $userid; + if (!empty($launchid)) { + $data->launchid = $launchid; + } else { + $data->launchid = mt_rand(); + } + + $json = json_encode($data); + + $hash = hash('sha256', $json . $servicesalt, false); + + $container = new stdClass(); + $container->data = $data; + $container->hash = $hash; + + return $container; +} + +/** + * This function builds the request that must be sent to the tool producer + * + * @param object $instance Basic LTI instance object + * @param object $typeconfig Basic LTI tool configuration + * @param object $course Course object + * + * @return array $request Request details + */ +function lti_build_request($instance, $typeconfig, $course) { + global $USER, $CFG; + + if (empty($instance->cmid)) { + $instance->cmid = 0; + } + + $role = lti_get_ims_role($USER, $instance->cmid, $instance->course); + + $locale = $course->lang; + if ( strlen($locale) < 1 ) { + $locale = $CFG->lang; + } + + $requestparams = array( + "resource_link_id" => $instance->id, + "resource_link_title" => $instance->name, + "resource_link_description" => $instance->intro, + "user_id" => $USER->id, + "roles" => $role, + "context_id" => $course->id, + "context_label" => $course->shortname, + "context_title" => $course->fullname, + "launch_presentation_locale" => $locale, + ); + + $placementsecret = $instance->servicesalt; + + if ( isset($placementsecret) ) { + $sourcedid = json_encode(lti_build_sourcedid($instance->id, $USER->id, null, $placementsecret)); + } + + if ( isset($placementsecret) && + ( $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS || + ( $typeconfig['acceptgrades'] == LTI_SETTING_DELEGATE && $instance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS ) ) ) { + $requestparams["lis_result_sourcedid"] = $sourcedid; + + $serviceurl = $CFG->wwwroot . '/mod/lti/service.php'; + if ($typeconfig['forcessl'] == '1') { + $serviceurl = lti_ensure_url_is_https($serviceurl); + } + + $requestparams["ext_ims_lis_basic_outcome_url"] = $serviceurl; + } + + /*if ( isset($placementsecret) && + ( $typeconfig['allowroster'] == LTI_SETTING_ALWAYS || + ( $typeconfig['allowroster'] == LTI_SETTING_DELEGATE && $instance->instructorchoiceallowroster == LTI_SETTING_ALWAYS ) ) ) { + $requestparams["ext_ims_lis_memberships_id"] = $sourcedid; + $requestparams["ext_ims_lis_memberships_url"] = $CFG->wwwroot.'/mod/lti/service.php'; + }*/ + + // Send user's name and email data if appropriate + if ( $typeconfig['sendname'] == LTI_SETTING_ALWAYS || + ( $typeconfig['sendname'] == LTI_SETTING_DELEGATE && $instance->instructorchoicesendname == LTI_SETTING_ALWAYS ) ) { + $requestparams["lis_person_name_given"] = $USER->firstname; + $requestparams["lis_person_name_family"] = $USER->lastname; + $requestparams["lis_person_name_full"] = $USER->firstname." ".$USER->lastname; + } + + if ( $typeconfig['sendemailaddr'] == LTI_SETTING_ALWAYS || + ( $typeconfig['sendemailaddr'] == LTI_SETTING_DELEGATE && $instance->instructorchoicesendemailaddr == LTI_SETTING_ALWAYS ) ) { + $requestparams["lis_person_contact_email_primary"] = $USER->email; + } + + //Add outcome service URL + $url = new moodle_url('/mod/lti/service.php'); + $requestparams['lis_outcome_service_url'] = $url->out(); + + // Concatenate the custom parameters from the administrator and the instructor + // Instructor parameters are only taken into consideration if the administrator + // has giver permission + $customstr = $typeconfig['customparameters']; + $instructorcustomstr = $instance->instructorcustomparameters; + $custom = array(); + $instructorcustom = array(); + if ($customstr) { + $custom = lti_split_custom_parameters($customstr); + } + if (!isset($typeconfig['allowinstructorcustom']) || $typeconfig['allowinstructorcustom'] == LTI_SETTING_NEVER) { + $requestparams = array_merge($custom, $requestparams); + } else { + if ($instructorcustomstr) { + $instructorcustom = lti_split_custom_parameters($instructorcustomstr); + } + foreach ($instructorcustom as $key => $val) { + // Ignore the instructor's parameter + if (!array_key_exists($key, $custom)) { + $custom[$key] = $val; + } + } + $requestparams = array_merge($custom, $requestparams); + } + + // Make sure we let the tool know what LMS they are being called from + $requestparams["ext_lms"] = "moodle-2"; + + // Add oauth_callback to be compliant with the 1.0A spec + $requestparams["oauth_callback"] = "about:blank"; + + //The submit button needs to be part of the signature as it gets posted with the form. + //This needs to be here to support launching without javascript. + $submittext = get_string('press_to_submit', 'lti'); + $requestparams["ext_submit"] = $submittext; + + $requestparams["lti_version"] = "LTI-1p0"; + $requestparams["lti_message_type"] = "basic-lti-launch-request"; + /* Suppress this for now - Chuck + if ( $orgdesc ) $requestparams["tool_consumer_instance_description"] = $orgdesc; + */ + + return $requestparams; +} + +function lti_get_tool_table($tools, $id) { + global $CFG, $USER; + $html = ''; + + $typename = get_string('typename', 'lti'); + $baseurl = get_string('baseurl', 'lti'); + $action = get_string('action', 'lti'); + $createdon = get_string('createdon', 'lti'); + + if ($id == 'lti_configured') { + $html .= '
'.get_string('addtype', 'lti').'
'; + } + + if (!empty($tools)) { + $html .= " +
+ + + + + + + + + + "; + + foreach ($tools as $type) { + $date = userdate($type->timecreated); + $accept = get_string('accept', 'lti'); + $update = get_string('update', 'lti'); + $delete = get_string('delete', 'lti'); + + $accepthtml = " + wwwroot}/mod/lti/typessettings.php?action=accept&id={$type->id}&sesskey={$USER->sesskey}&tab={$id}\" title=\"{$accept}\"> + \"{$accept}\"wwwroot}/pix/t/clear.gif\"/> + + "; + + $deleteaction = 'delete'; + + if ($type->state == LTI_TOOL_STATE_CONFIGURED) { + $accepthtml = ''; + } + + if ($type->state != LTI_TOOL_STATE_REJECTED) { + $deleteaction = 'reject'; + $delete = get_string('reject', 'lti'); + } + + $html .= " + + + + + + + "; + } + $html .= '
$typename$baseurl$createdon$action
+ {$type->name} + + {$type->baseurl} + + {$date} + + {$accepthtml} + wwwroot}/mod/lti/typessettings.php?action=update&id={$type->id}&sesskey={$USER->sesskey}&tab={$id}\" title=\"{$update}\"> + \"{$update}\"wwwroot}/pix/t/edit.gif\"/> + + wwwroot}/mod/lti/typessettings.php?action={$deleteaction}&id={$type->id}&sesskey={$USER->sesskey}&tab={$id}\" title=\"{$delete}\"> + \"{$delete}\"wwwroot}/pix/t/delete.gif\"/> + +
'; + } else { + $html .= get_string('no_' . $id, 'lti'); + } + + return $html; +} + +/** + * Splits the custom parameters field to the various parameters + * + * @param string $customstr String containing the parameters + * + * @return Array of custom parameters + */ +function lti_split_custom_parameters($customstr) { + $textlib = textlib_get_instance(); + + $lines = preg_split("/[\n;]/", $customstr); + $retval = array(); + foreach ($lines as $line) { + $pos = strpos($line, "="); + if ( $pos === false || $pos < 1 ) { + continue; + } + $key = trim($textlib->substr($line, 0, $pos)); + $val = trim($textlib->substr($line, $pos+1)); + $key = lti_map_keyname($key); + $retval['custom_'.$key] = $val; + } + return $retval; +} + +/** + * Used for building the names of the different custom parameters + * + * @param string $key Parameter name + * + * @return string Processed name + */ +function lti_map_keyname($key) { + $textlib = textlib_get_instance(); + + $newkey = ""; + $key = $textlib->strtolower(trim($key)); + foreach (str_split($key) as $ch) { + if ( ($ch >= 'a' && $ch <= 'z') || ($ch >= '0' && $ch <= '9') ) { + $newkey .= $ch; + } else { + $newkey .= '_'; + } + } + return $newkey; +} + +/** + * Gets the IMS role string for the specified user and LTI course module. + * + * @param mixed $user User object or user id + * @param int $cmid The course module id of the LTI activity + * @return string A role string suitable for passing with an LTI launch + */ +function lti_get_ims_role($user, $cmid, $courseid) { + $roles = array(); + + if (empty($cmid)) { + //If no cmid is passed, check if the user is a teacher in the course + //This allows other modules to programmatically "fake" a launch without + //a real LTI instance + $coursecontext = get_context_instance(CONTEXT_COURSE, $courseid); + + if (has_capability('moodle/course:manageactivities', $coursecontext)) { + array_push($roles, 'Instructor'); + } else { + array_push($roles, 'Learner'); + } + } else { + $context = get_context_instance(CONTEXT_MODULE, $cmid); + + if (has_capability('mod/lti:manage', $context)) { + array_push($roles, 'Instructor'); + } else { + array_push($roles, 'Learner'); + } + } + + if (is_siteadmin($user)) { + array_push($roles, 'urn:lti:sysrole:ims/lis/Administrator'); + } + + return join(',', $roles); +} + +/** + * Returns configuration details for the tool + * + * @param int $typeid Basic LTI tool typeid + * + * @return array Tool Configuration + */ +function lti_get_type_config($typeid) { + global $DB; + + $query = "SELECT name, value + FROM {lti_types_config} + WHERE typeid = :typeid1 + UNION ALL + SELECT 'toolurl' AS name, baseurl AS value + FROM {lti_types} + WHERE id = :typeid2"; + + $typeconfig = array(); + $configs = $DB->get_records_sql($query, array('typeid1' => $typeid, 'typeid2' => $typeid)); + + if (!empty($configs)) { + foreach ($configs as $config) { + $typeconfig[$config->name] = $config->value; + } + } + + return $typeconfig; +} + +function lti_get_tools_by_url($url, $state, $courseid = null) { + $domain = lti_get_domain_from_url($url); + + return lti_get_tools_by_domain($domain, $state, $courseid); +} + +function lti_get_tools_by_domain($domain, $state = null, $courseid = null) { + global $DB, $SITE; + + $filters = array('tooldomain' => $domain); + + $statefilter = ''; + $coursefilter = ''; + + if ($state) { + $statefilter = 'AND state = :state'; + } + + if ($courseid && $courseid != $SITE->id) { + $coursefilter = 'OR course = :courseid'; + } + + $query = "SELECT * + FROM {lti_types} + WHERE tooldomain = :tooldomain + AND (course = :siteid $coursefilter) + $statefilter"; + + return $DB->get_records_sql($query, array( + 'courseid' => $courseid, + 'siteid' => $SITE->id, + 'tooldomain' => $domain, + 'state' => $state + )); +} + +/** + * Returns all basicLTI tools configured by the administrator + * + */ +function lti_filter_get_types($course) { + global $DB; + + if (!empty($course)) { + $filter = array('course' => $course); + } else { + $filter = array(); + } + + return $DB->get_records('lti_types', $filter); +} + +function lti_get_types_for_add_instance() { + global $DB, $SITE, $COURSE; + + $query = "SELECT * + FROM {lti_types} + WHERE coursevisible = 1 + AND (course = :siteid OR course = :courseid) + AND state = :active"; + + $admintypes = $DB->get_records_sql($query, array('siteid' => $SITE->id, 'courseid' => $COURSE->id, 'active' => LTI_TOOL_STATE_CONFIGURED)); + + $types = array(); + $types[0] = (object)array('name' => get_string('automatic', 'lti'), 'course' => $SITE->id); + + foreach ($admintypes as $type) { + $types[$type->id] = $type; + } + + return $types; +} + +function lti_get_domain_from_url($url) { + $matches = array(); + + if (preg_match(LTI_URL_DOMAIN_REGEX, $url, $matches)) { + return $matches[1]; + } +} + +function lti_get_tool_by_url_match($url, $courseid = null, $state = LTI_TOOL_STATE_CONFIGURED) { + $possibletools = lti_get_tools_by_url($url, $state, $courseid); + + return lti_get_best_tool_by_url($url, $possibletools, $courseid); +} + +function lti_get_url_thumbprint($url) { + $urlparts = parse_url(strtolower($url)); + if (!isset($urlparts['path'])) { + $urlparts['path'] = ''; + } + + if (!isset($urlparts['host'])) { + $urlparts['host'] = ''; + } + + if (substr($urlparts['host'], 0, 4) === 'www.') { + $urlparts['host'] = substr($urlparts['host'], 4); + } + + return $urllower = $urlparts['host'] . '/' . $urlparts['path']; +} + +function lti_get_best_tool_by_url($url, $tools, $courseid = null) { + if (count($tools) === 0) { + return null; + } + + $urllower = lti_get_url_thumbprint($url); + + foreach ($tools as $tool) { + $tool->_matchscore = 0; + + $toolbaseurllower = lti_get_url_thumbprint($tool->baseurl); + + if ($urllower === $toolbaseurllower) { + //100 points for exact thumbprint match + $tool->_matchscore += 100; + } else if (substr($urllower, 0, strlen($toolbaseurllower)) === $toolbaseurllower) { + //50 points if tool thumbprint starts with the base URL thumbprint + $tool->_matchscore += 50; + } + + //Prefer course tools over site tools + if (!empty($courseid)) { + //Minus 25 points for not matching the course id (global tools) + if ($tool->course != $courseid) { + $tool->_matchscore -= 10; + } + } + } + + $bestmatch = array_reduce($tools, function($value, $tool) { + if ($tool->_matchscore > $value->_matchscore) { + return $tool; + } else { + return $value; + } + + }, (object)array('_matchscore' => -1)); + + //None of the tools are suitable for this URL + if ($bestmatch->_matchscore <= 0) { + return null; + } + + return $bestmatch; +} + +function lti_get_shared_secrets_by_key($key) { + global $DB; + + //Look up the shared secret for the specified key in both the types_config table (for configured tools) + //And in the lti resource table for ad-hoc tools + $query = "SELECT t2.value + FROM {lti_types_config} t1 + JOIN {lti_types_config} t2 ON t1.typeid = t2.typeid + JOIN {lti_types} type ON t2.typeid = type.id + WHERE t1.name = 'resourcekey' + AND t1.value = :key1 + AND t2.name = 'password' + AND type.state = :configured + UNION + SELECT password AS value + FROM {lti} + WHERE resourcekey = :key2"; + + $sharedsecrets = $DB->get_records_sql($query, array('configured' => LTI_TOOL_STATE_CONFIGURED, 'key1' => $key, 'key2' => $key)); + + $values = array_map(function($item) { + return $item->value; + }, $sharedsecrets); + + //There should really only be one shared secret per key. But, we can't prevent + //more than one getting entered. For instance, if the same key is used for two tool providers. + return $values; +} + +/** + * Prints the various configured tool types + * + */ +function lti_filter_print_types() { + global $CFG; + + $types = lti_filter_get_types(); + if (!empty($types)) { + echo ''; + } else { + echo '
'; + echo get_string('notypes', 'lti'); + echo '
'; + } +} + +/** + * Delete a Basic LTI configuration + * + * @param int $id Configuration id + */ +function lti_delete_type($id) { + global $DB; + + //We should probably just copy the launch URL to the tool instances in this case... using a single query + /* + $instances = $DB->get_records('lti', array('typeid' => $id)); + foreach ($instances as $instance) { + $instance->typeid = 0; + $DB->update_record('lti', $instance); + }*/ + + $DB->delete_records('lti_types', array('id' => $id)); + $DB->delete_records('lti_types_config', array('typeid' => $id)); +} + +function lti_set_state_for_type($id, $state) { + global $DB; + + $DB->update_record('lti_types', array('id' => $id, 'state' => $state)); +} + +/** + * Transforms a basic LTI object to an array + * + * @param object $ltiobject Basic LTI object + * + * @return array Basic LTI configuration details + */ +function lti_get_config($ltiobject) { + $typeconfig = array(); + $typeconfig = (array)$ltiobject; + $additionalconfig = lti_get_type_config($ltiobject->typeid); + $typeconfig = array_merge($typeconfig, $additionalconfig); + return $typeconfig; +} + +/** + * + * Generates some of the tool configuration based on the instance details + * + * @param int $id + * + * @return Instance configuration + * + */ +function lti_get_type_config_from_instance($id) { + global $DB; + + $instance = $DB->get_record('lti', array('id' => $id)); + $config = lti_get_config($instance); + + $type = new stdClass(); + $type->lti_fix = $id; + if (isset($config['toolurl'])) { + $type->lti_toolurl = $config['toolurl']; + } + if (isset($config['instructorchoicesendname'])) { + $type->lti_sendname = $config['instructorchoicesendname']; + } + if (isset($config['instructorchoicesendemailaddr'])) { + $type->lti_sendemailaddr = $config['instructorchoicesendemailaddr']; + } + if (isset($config['instructorchoiceacceptgrades'])) { + $type->lti_acceptgrades = $config['instructorchoiceacceptgrades']; + } + if (isset($config['instructorchoiceallowroster'])) { + $type->lti_allowroster = $config['instructorchoiceallowroster']; + } + + if (isset($config['instructorcustomparameters'])) { + $type->lti_allowsetting = $config['instructorcustomparameters']; + } + return $type; +} + +/** + * Generates some of the tool configuration based on the admin configuration details + * + * @param int $id + * + * @return Configuration details + */ +function lti_get_type_type_config($id) { + global $DB; + + $basicltitype = $DB->get_record('lti_types', array('id' => $id)); + $config = lti_get_type_config($id); + + $type->lti_typename = $basicltitype->name; + + $type->typeid = $basicltitype->id; + + $type->lti_toolurl = $basicltitype->baseurl; + + if (isset($config['resourcekey'])) { + $type->lti_resourcekey = $config['resourcekey']; + } + if (isset($config['password'])) { + $type->lti_password = $config['password']; + } + + if (isset($config['sendname'])) { + $type->lti_sendname = $config['sendname']; + } + if (isset($config['instructorchoicesendname'])) { + $type->lti_instructorchoicesendname = $config['instructorchoicesendname']; + } + if (isset($config['sendemailaddr'])) { + $type->lti_sendemailaddr = $config['sendemailaddr']; + } + if (isset($config['instructorchoicesendemailaddr'])) { + $type->lti_instructorchoicesendemailaddr = $config['instructorchoicesendemailaddr']; + } + if (isset($config['acceptgrades'])) { + $type->lti_acceptgrades = $config['acceptgrades']; + } + if (isset($config['instructorchoiceacceptgrades'])) { + $type->lti_instructorchoiceacceptgrades = $config['instructorchoiceacceptgrades']; + } + if (isset($config['allowroster'])) { + $type->lti_allowroster = $config['allowroster']; + } + if (isset($config['instructorchoiceallowroster'])) { + $type->lti_instructorchoiceallowroster = $config['instructorchoiceallowroster']; + } + + if (isset($config['customparameters'])) { + $type->lti_customparameters = $config['customparameters']; + } + + if (isset($config['forcessl'])) { + $type->lti_forcessl = $config['forcessl']; + } + + if (isset($config['organizationid'])) { + $type->lti_organizationid = $config['organizationid']; + } + if (isset($config['organizationurl'])) { + $type->lti_organizationurl = $config['organizationurl']; + } + if (isset($config['organizationdescr'])) { + $type->lti_organizationdescr = $config['organizationdescr']; + } + if (isset($config['launchcontainer'])) { + $type->lti_launchcontainer = $config['launchcontainer']; + } + + if (isset($config['coursevisible'])) { + $type->lti_coursevisible = $config['coursevisible']; + } + + if (isset($config['debuglaunch'])) { + $type->lti_debuglaunch = $config['debuglaunch']; + } + + if (isset($config['module_class_type'])) { + $type->lti_module_class_type = $config['module_class_type']; + } + + return $type; +} + +function lti_prepare_type_for_save($type, $config) { + $type->baseurl = $config->lti_toolurl; + $type->tooldomain = lti_get_domain_from_url($config->lti_toolurl); + $type->name = $config->lti_typename; + + $type->coursevisible = !empty($config->lti_coursevisible) ? $config->lti_coursevisible : 0; + $config->lti_coursevisible = $type->coursevisible; + + $type->forcessl = !empty($config->lti_forcessl) ? $config->lti_forcessl : 0; + $config->lti_forcessl = $type->forcessl; + + $type->timemodified = time(); + + unset ($config->lti_typename); + unset ($config->lti_toolurl); +} + +function lti_update_type($type, $config) { + global $DB; + + lti_prepare_type_for_save($type, $config); + + if ($DB->update_record('lti_types', $type)) { + foreach ($config as $key => $value) { + if (substr($key, 0, 4)=='lti_' && !is_null($value)) { + $record = new StdClass(); + $record->typeid = $type->id; + $record->name = substr($key, 4); + $record->value = $value; + + lti_update_config($record); + } + } + } +} + +function lti_add_type($type, $config) { + global $USER, $SITE, $DB; + + lti_prepare_type_for_save($type, $config); + + if (!isset($type->state)) { + $type->state = LTI_TOOL_STATE_PENDING; + } + + if (!isset($type->timecreated)) { + $type->timecreated = time(); + } + + if (!isset($type->createdby)) { + $type->createdby = $USER->id; + } + + if (!isset($type->course)) { + $type->course = $SITE->id; + } + + //Create a salt value to be used for signing passed data to extension services + //The outcome service uses the service salt on the instance. This can be used + //for communication with services not related to a specific LTI instance. + $config->lti_servicesalt = uniqid('', true); + + $id = $DB->insert_record('lti_types', $type); + + if ($id) { + foreach ($config as $key => $value) { + if (substr($key, 0, 4)=='lti_' && !is_null($value)) { + $record = new StdClass(); + $record->typeid = $id; + $record->name = substr($key, 4); + $record->value = $value; + + lti_add_config($record); + } + } + } + + return $id; +} + +/** + * Add a tool configuration in the database + * + * @param $config Tool configuration + * + * @return int Record id number + */ +function lti_add_config($config) { + global $DB; + + return $DB->insert_record('lti_types_config', $config); +} + +/** + * Updates a tool configuration in the database + * + * @param $config Tool configuration + * + * @return Record id number + */ +function lti_update_config($config) { + global $DB; + + $return = true; + $old = $DB->get_record('lti_types_config', array('typeid' => $config->typeid, 'name' => $config->name)); + + if ($old) { + $config->id = $old->id; + $return = $DB->update_record('lti_types_config', $config); + } else { + $return = $DB->insert_record('lti_types_config', $config); + } + return $return; +} + +/** + * Signs the petition to launch the external tool using OAuth + * + * @param $oldparms Parameters to be passed for signing + * @param $endpoint url of the external tool + * @param $method Method for sending the parameters (e.g. POST) + * @param $oauth_consumoer_key Key + * @param $oauth_consumoer_secret Secret + * @param $submittext The text for the submit button + * @param $orgid LMS name + * @param $orgdesc LMS key + */ +function lti_sign_parameters($oldparms, $endpoint, $method, $oauthconsumerkey, $oauthconsumersecret) { + //global $lastbasestring; + $parms = $oldparms; + + $testtoken = ''; + + // TODO: Switch to core oauthlib once implemented - MDL-30149 + $hmacmethod = new lti\OAuthSignatureMethod_HMAC_SHA1(); + $testconsumer = new lti\OAuthConsumer($oauthconsumerkey, $oauthconsumersecret, null); + $accreq = lti\OAuthRequest::from_consumer_and_token($testconsumer, $testtoken, $method, $endpoint, $parms); + $accreq->sign_request($hmacmethod, $testconsumer, $testtoken); + + // Pass this back up "out of band" for debugging + //$lastbasestring = $accreq->get_signature_base_string(); + + $newparms = $accreq->get_parameters(); + + return $newparms; +} + +/** + * Posts the launch petition HTML + * + * @param $newparms Signed parameters + * @param $endpoint URL of the external tool + * @param $debug Debug (true/false) + */ +function lti_post_launch_html($newparms, $endpoint, $debug=false) { + //global $lastbasestring; + + $r = "
\n"; + + $submittext = $newparms['ext_submit']; + + // Contruct html for the launch parameters + foreach ($newparms as $key => $value) { + $key = htmlspecialchars($key); + $value = htmlspecialchars($value); + if ( $key == "ext_submit" ) { + $r .= "\n"; + } + + if ( $debug ) { + $r .= "\n"; + $r .= ""; + $r .= get_string("toggle_debug_data", "lti")."\n"; + $r .= "
\n"; + $r .= "".get_string("basiclti_endpoint", "lti")."
\n"; + $r .= $endpoint . "
\n 
\n"; + $r .= "".get_string("basiclti_parameters", "lti")."
\n"; + foreach ($newparms as $key => $value) { + $key = htmlspecialchars($key); + $value = htmlspecialchars($value); + $r .= "$key = $value
\n"; + } + $r .= " 
\n"; + //$r .= "

".get_string("basiclti_base_string", "lti")."
\n".$lastbasestring."

\n"; + $r .= "
\n"; + } + $r .= "
\n"; + + if ( ! $debug ) { + $ext_submit = "ext_submit"; + $ext_submit_text = $submittext; + $r .= " \n"; + } + return $r; +} + +function lti_get_type($typeid) { + global $DB; + + return $DB->get_record('lti_types', array('id' => $typeid)); +} + +function lti_get_launch_container($lti, $toolconfig) { + if (empty($lti->launchcontainer)) { + $lti->launchcontainer = LTI_LAUNCH_CONTAINER_DEFAULT; + } + + if ($lti->launchcontainer == LTI_LAUNCH_CONTAINER_DEFAULT) { + if (isset($toolconfig['launchcontainer'])) { + $launchcontainer = $toolconfig['launchcontainer']; + } + } else { + $launchcontainer = $lti->launchcontainer; + } + + if (empty($launchcontainer) || $launchcontainer == LTI_LAUNCH_CONTAINER_DEFAULT) { + $launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS; + } + + $devicetype = get_device_type(); + + //Scrolling within the object element doesn't work on iOS or Android + //Opening the popup window also had some issues in testing + //For mobile devices, always take up the entire screen to ensure the best experience + if ($devicetype === 'mobile' || $devicetype === 'tablet' ) { + $launchcontainer = LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW; + } + + return $launchcontainer; +} + +function lti_request_is_using_ssl() { + global $ME; + return (stripos($ME, 'https://') === 0); +} + +function lti_ensure_url_is_https($url) { + if (!strstr($url, '://')) { + $url = 'https://' . $url; + } else { + //If the URL starts with http, replace with https + if (stripos($url, 'http://') === 0) { + $url = 'https://' . substr($url, 8); + } + } + + return $url; +} diff --git a/mod/lti/mod_form.js b/mod/lti/mod_form.js new file mode 100644 index 00000000000..46b8718457c --- /dev/null +++ b/mod/lti/mod_form.js @@ -0,0 +1,350 @@ +// 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 . + +/** + * MRTODO: Brief description of this file + * + * @package mod + * @subpackage lti + * @copyright 2011 onwards MRTODO + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +(function(){ + var Y; + + M.mod_lti = M.mod_lti || {}; + + M.mod_lti.editor = { + init: function(yui3, settings){ + if(yui3){ + Y = yui3; + } + + var self = this; + this.settings = Y.JSON.parse(settings); + + this.urlCache = {}; + + this.addOptGroups(); + + var updateToolMatches = function(){ + self.updateAutomaticToolMatch(Y.one('#id_toolurl')); + self.updateAutomaticToolMatch(Y.one('#id_securetoolurl')); + }; + + var typeSelector = Y.one('#id_typeid'); + typeSelector.on('change', function(e){ + updateToolMatches(); + + self.toggleEditButtons(); + }); + + this.createTypeEditorButtons(); + + this.toggleEditButtons(); + + var textAreas = new Y.NodeList([ + Y.one('#id_toolurl'), + Y.one('#id_securetoolurl'), + Y.one('#id_resourcekey'), + Y.one('#id_password') + ]); + + var debounce; + textAreas.on('keyup', function(e){ + clearTimeout(debounce); + + //If no more changes within 2 seconds, look up the matching tool URL + debounce = setTimeout(function(){ + updateToolMatches(); + }, 2000); + }); + + updateToolMatches(); + }, + + clearToolCache: function(){ + this.urlCache = {}; + }, + + updateAutomaticToolMatch: function(field){ + var self = this; + + var toolurl = field; + var typeSelector = Y.one('#id_typeid'); + + var id = field.get('id') + '_lti_automatch_tool'; + var automatchToolDisplay = Y.one('#' + id); + + if(!automatchToolDisplay){ + automatchToolDisplay = Y.Node.create('') + .set('id', id) + .setStyle('padding-left', '1em'); + + toolurl.insert(automatchToolDisplay, 'after'); + } + + var url = toolurl.get('value'); + + //Hide the display if the url box is empty + if(!url){ + automatchToolDisplay.setStyle('display', 'none'); + } else { + automatchToolDisplay.set('innerHTML', ''); + automatchToolDisplay.setStyle('display', ''); + } + + var selectedToolType = typeSelector.get('value'); + var selectedOption = typeSelector.one('option[value="' + selectedToolType + '"]'); + + //A specific tool type is selected (not "auto") + if(selectedToolType > 0){ + //If the entered domain matches the domain of the tool configuration... + var domainRegex = /(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i; + var match = domainRegex.exec(url); + if(match && match[1] && match[1].toLowerCase() === selectedOption.getAttribute('domain').toLowerCase()){ + automatchToolDisplay.set('innerHTML', '' + M.str.lti.using_tool_configuration + selectedOption.get('text')); + } else { + //The entered URL does not match the domain of the tool configuration + automatchToolDisplay.set('innerHTML', '' + M.str.lti.domain_mismatch); + } + + return; + } + + var key = Y.one('#id_resourcekey'); + var secret = Y.one('#id_password'); + + //We don't care what tool type this tool is associated with if it's manually configured' + if(key.get('value') !== '' && secret.get('value') !== ''){ + automatchToolDisplay.set('innerHTML', '' + M.str.lti.custom_config); + } else { + var continuation = function(toolInfo){ + if(toolInfo.toolname){ + automatchToolDisplay.set('innerHTML', '' + M.str.lti.using_tool_configuration + toolInfo.toolname); + } else { + //Inform them custom configuration is in use + if(key.get('value') === '' || secret.get('value') === ''){ + automatchToolDisplay.set('innerHTML', '' + M.str.lti.tool_config_not_found); + } + } + }; + + //Cache urls which have already been checked to increaes performance + if(self.urlCache[url]){ + continuation(self.urlCache[url]); + } else { + self.findToolByUrl(url, function(toolInfo){ + self.urlCache[url] = toolInfo; + + continuation(toolInfo); + }); + } + } + }, + + getSelectedToolTypeOption: function(){ + var typeSelector = Y.one('#id_typeid'); + + return typeSelector.one('option[value="' + typeSelector.get('value') + '"]'); + }, + + /** + * Separate tool listing into option groups. Server-side select control + * doesn't seem to support this. + */ + addOptGroups: function(){ + var typeSelector = Y.one('#id_typeid'); + + if(typeSelector.one('option[courseTool=1]')){ + //One ore more course tools exist + + var globalGroup = Y.Node.create('') + .set('id', 'global_tool_group') + .set('label', M.str.lti.global_tool_types); + + var courseGroup = Y.Node.create('') + .set('id', 'course_tool_group') + .set('label', M.str.lti.course_tool_types); + + var globalOptions = typeSelector.all('option[globalTool=1]').remove().each(function(node){ + globalGroup.append(node); + }); + + var courseOptions = typeSelector.all('option[courseTool=1]').remove().each(function(node){ + courseGroup.append(node); + }); + + if(globalOptions.size() > 0){ + typeSelector.append(globalGroup); + } + + if(courseOptions.size() > 0){ + typeSelector.append(courseGroup); + } + } + }, + + /** + * Adds buttons for creating, editing, and deleting tool types. + * Javascript is a requirement to edit course level tools at this point. + */ + createTypeEditorButtons: function(){ + var self = this; + + var typeSelector = Y.one('#id_typeid'); + + var createIcon = function(id, tooltip, iconUrl){ + return Y.Node.create('') + .set('id', id) + .set('title', tooltip) + .setStyle('margin-left', '.5em') + .set('href', 'javascript:void(0);') + .append(Y.Node.create('')); + } + + var addIcon = createIcon('lti_add_tool_type', M.str.lti.addtype, this.settings.add_icon_url); + var editIcon = createIcon('lti_edit_tool_type', M.str.lti.edittype, this.settings.edit_icon_url); + var deleteIcon = createIcon('lti_delete_tool_type', M.str.lti.deletetype, this.settings.delete_icon_url); + + editIcon.on('click', function(e){ + var toolTypeId = typeSelector.get('value'); + + if(self.getSelectedToolTypeOption().getAttribute('editable')){ + window.open(self.settings.instructor_tool_type_edit_url + '&action=edit&typeid=' + toolTypeId, 'edit_tool'); + } else { + alert(M.str.lti.cannot_edit); + } + }); + + addIcon.on('click', function(e){ + window.open(self.settings.instructor_tool_type_edit_url + '&action=add', 'add_tool'); + }); + + deleteIcon.on('click', function(e){ + var toolTypeId = typeSelector.get('value'); + + if(self.getSelectedToolTypeOption().getAttribute('editable')){ + if(confirm(M.str.lti.delete_confirmation)){ + self.deleteTool(toolTypeId); + } + } else { + alert(M.str.lti.cannot_delete); + } + }); + + typeSelector.insert(addIcon, 'after'); + addIcon.insert(editIcon, 'after'); + editIcon.insert(deleteIcon, 'after'); + }, + + toggleEditButtons: function(){ + var lti_edit_tool_type = Y.one('#lti_edit_tool_type'); + var lti_delete_tool_type = Y.one('#lti_delete_tool_type'); + + //Make the edit / delete icons look enabled / disabled. + //Does not work in older browsers, but alerts will catch those cases. + if(this.getSelectedToolTypeOption().getAttribute('editable')){ + lti_edit_tool_type.setStyle('opacity', '1'); + lti_delete_tool_type.setStyle('opacity', '1'); + } else { + lti_edit_tool_type.setStyle('opacity', '.2'); + lti_delete_tool_type.setStyle('opacity', '.2'); + } + }, + + addToolType: function(toolType){ + var typeSelector = Y.one('#id_typeid'); + var course_tool_group = Y.one('#course_tool_group'); + + var option = Y.Node.create('
+ +
+
+ $configuredtoolshtml +
+
+ $pendingtoolshtml +
+
+ $rejectedtoolshtml +
+
+
+ + +"; + global $PAGE; // TODO: Move to YUI3 ASAP + $PAGE->requires->yui2_lib('tabview'); + $PAGE->requires->yui2_lib('datatable'); + + $settings->add(new admin_setting_heading('lti_types', get_string('external_tool_types', 'lti') . $OUTPUT->help_icon('main_admin', 'lti'), $template)); +} diff --git a/mod/lti/simpletest/testlocallib.php b/mod/lti/simpletest/testlocallib.php new file mode 100644 index 00000000000..990dbf40787 --- /dev/null +++ b/mod/lti/simpletest/testlocallib.php @@ -0,0 +1,129 @@ +. +// +// 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 unit tests for (some of) lti/locallib.php + * + * @package mod + * @subpackage lti + * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis + * marc.alier@upc.edu + * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu + * @author Charles Severance csev@unmich.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/locallib.php'); +require_once($CFG->dirroot . '/mod/lti/servicelib.php'); + +class lti_locallib_test extends UnitTestCase { + public static $includecoverage = array('mod/lti/locallib.php'); + + public function test_split_custom_parameters() { + $this->assertEqual(lti_split_custom_parameters("x=1\ny=2"), + array('custom_x' => '1', 'custom_y'=> '2')); + + $this->assertEqual(lti_split_custom_parameters('x=1;y=2'), + array('custom_x' => '1', 'custom_y'=> '2')); + + $this->assertEqual(lti_split_custom_parameters('Review:Chapter=1.2.56'), + array('custom_review_chapter' => '1.2.56')); + + $this->assertEqual(lti_split_custom_parameters('Complex!@#$^*(){}[]KEY=Complex!@#$^*(){}[]Value'), + array('custom_complex____________key' => 'Complex!@#$^*(){}[]Value')); + } + + public function test_sign_parameters() { + $correct = array ( 'context_id' => '12345', 'context_label' => 'SI124', 'context_title' => 'Social Computing', 'ext_submit' => 'Click Me', 'lti_message_type' => 'basic-lti-launch-request', 'lti_version' => 'LTI-1p0', 'oauth_consumer_key' => 'lmsng.school.edu', 'oauth_nonce' => '47458148e33a8f9dafb888c3684cf476', 'oauth_signature' => 'qWgaBIezihCbeHgcwUy14tZcyDQ=', 'oauth_signature_method' => 'HMAC-SHA1', 'oauth_timestamp' => '1307141660', 'oauth_version' => '1.0', 'resource_link_id' => '123', 'resource_link_title' => 'Weekly Blog', 'roles' => 'Learner', 'tool_consumer_instance_guid' => 'lmsng.school.edu', 'user_id' => '789'); + + $requestparams = array('resource_link_id' => '123', 'resource_link_title' => 'Weekly Blog', 'user_id' => '789', 'roles' => 'Learner', 'context_id' => '12345', 'context_label' => 'SI124', 'context_title' => 'Social Computing'); + + $parms = lti_sign_parameters($requestparams, 'http://www.imsglobal.org/developer/LTI/tool.php', 'POST', + 'lmsng.school.edu', 'secret', 'Click Me', 'lmsng.school.edu' /*, $org_desc*/); + $this->assertTrue(isset($parms['oauth_nonce'])); + $this->assertTrue(isset($parms['oauth_signature'])); + $this->assertTrue(isset($parms['oauth_timestamp'])); + + // Those things that are hard to mock + $correct['oauth_nonce'] = $parms['oauth_nonce']; + $correct['oauth_signature'] = $parms['oauth_signature']; + $correct['oauth_timestamp'] = $parms['oauth_timestamp']; + ksort($parms); + ksort($correct); + $this->assertEqual($parms, $correct); + } + + public function test_parse_grade_replace_message() { + $message = ' + + + + V1.0 + 999998123 + + + + + + + {"data":{"instanceid":"2","userid":"2"},"hash":"0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b"} + + + + en-us + 0.92 + + + + + + +'; + + $parsed = lti_parse_grade_replace_message(new SimpleXMLElement($message)); + + $this->assertEqual($parsed->userid, '2'); + $this->assertEqual($parsed->instanceid, '2'); + $this->assertEqual($parsed->sourcedidhash, '0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b'); + + $ltiinstance = (object)array('servicesalt' => '4e5fcc06de1d58.44963230'); + + lti_verify_sourcedid($ltiinstance, $parsed); + } +} diff --git a/mod/lti/styles.css b/mod/lti/styles.css new file mode 100644 index 00000000000..470390b9cbf --- /dev/null +++ b/mod/lti/styles.css @@ -0,0 +1,35 @@ +.path-mod-lti .ltiframe {position: relative;width: 100%;height: 100%;} + +/** General Styles **/ +.path-mod-lti .userpicture, +.path-mod-lti .picture.user, +.path-mod-lti .picture.teacher {width:35px;height: 35px;vertical-align:top;} +.path-mod-lti .feedback .files, +.path-mod-lti .feedback .grade, +.path-mod-lti .feedback .outcome, +.path-mod-lti .feedback .finalgrade {float: right;} +.path-mod-lti .feedback .disabledfeedback {width: 500px;height: 250px;} +.path-mod-lti .feedback .from {float: left;} +.path-mod-lti .files img {margin-right: 4px;} +.path-mod-lti .files a {white-space:nowrap;} +.path-mod-lti .late {color: red;} +.path-mod-lti .message {text-align: center;} + +/** Styles for submissions.php **/ +#page-mod-lti-submissions fieldset.felement {margin-left: 16%;} +#page-mod-lti-submissions form#options div {text-align:right;margin-left:auto;margin-right:20px;} +#page-mod-lti-submissions .header .commands {display: inline;} +#page-mod-lti-submissions .picture {width: 35px;} +#page-mod-lti-submissions .fullname, +#page-mod-lti-submissions .timemodified, +#page-mod-lti-submissions .timemarked {text-align: left;} +#page-mod-lti-submissions .submissions .grade, +#page-mod-lti-submissions .submissions .outcome, +#page-mod-lti-submissions .submissions .finalgrade {text-align: right;} +#page-mod-lti-submissions .qgprefs #optiontable {text-align:right;margin-left:auto;} + +/* Styles for admin */ +.path-admin-mod-lti .mform .fitem .fitemtitle { min-width:18em;padding-right:1em } /* Prevent setting titles from wrapping */ + +/* Styles for instructor_edit_tool_type.php */ +#page-mod-lti-instructor_edit_tool_type .mform .fitem .fitemtitle { min-width:18em;padding-right:1em } /* Prevent setting titles from wrapping */ diff --git a/mod/lti/submissions.js b/mod/lti/submissions.js new file mode 100644 index 00000000000..80e1b0d73df --- /dev/null +++ b/mod/lti/submissions.js @@ -0,0 +1,73 @@ +// 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 . + +/** + * MRTODO: Brief description of this file + * + * @package mod + * @subpackage lti + * @copyright 2011 onwards MRTODO + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +(function(){ + var Y; + + M.mod_lti = M.mod_lti || {}; + + M.mod_lti.submissions = { + init: function(yui3){ + if(yui3){ + Y = yui3; + } + + this.setupTable(); + }, + + setupTable: function(){ + var lti_submissions_table = YAHOO.util.Dom.get('lti_submissions_table'); + + var dataSource = new YAHOO.util.DataSource(lti_submissions_table); + + var configuredColumns = [ + { key: "user", label: "User", sortable:true }, + { key: "date", label: "Submission Date", sortable:true, formatter: 'date' }, + { key: "grade", + label: "Grade", + sortable:true, + formatter: function(cell, record, column, data){ + cell.innerHTML = parseFloat(data).toFixed(1) + '%'; + } + } + ]; + + dataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; + dataSource.responseSchema = { + fields: [ + { key: "user" }, + { key: "date", parser: "date" }, + { key: "grade", parser: "number" }, + ] + }; + + new YAHOO.widget.DataTable("lti_submissions_table_container", configuredColumns, dataSource, + { + sortedBy: {key:"date", dir:"desc"} + } + ); + + Y.one('#lti_submissions_table_container').setStyle('display', ''); + } + } +})(); diff --git a/mod/lti/typessettings.php b/mod/lti/typessettings.php new file mode 100644 index 00000000000..0a2f8560ce7 --- /dev/null +++ b/mod/lti/typessettings.php @@ -0,0 +1,192 @@ +. +// +// 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 script used to clone Moodle admin setting page. + * It is used to create a new form used to pre-configure lti activities + * + * @package mod + * @subpackage 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 + */ + +require_once('../../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->dirroot.'/mod/lti/edit_form.php'); +require_once($CFG->dirroot.'/mod/lti/locallib.php'); + +$section = 'modsettinglti'; +$return = optional_param('return', '', PARAM_ALPHA); +$adminediting = optional_param('adminedit', -1, PARAM_BOOL); +$action = optional_param('action', null, PARAM_ACTION); +$id = optional_param('id', null, PARAM_INT); +$useexisting = optional_param('useexisting', null, PARAM_INT); +$definenew = optional_param('definenew', null, PARAM_INT); + +// no guest autologin +require_login(0, false); +$url = new moodle_url('/mod/lti/typesettings.php'); +$PAGE->set_url($url); + +admin_externalpage_setup('managemodules'); // Hacky solution for printing the admin page + +$tab = optional_param('tab', '', PARAM_ALPHAEXT); +$redirect = "$CFG->wwwroot/$CFG->admin/settings.php?section=modsettinglti&tab={$tab}"; + +// WRITING SUBMITTED DATA (IF ANY) + +$statusmsg = ''; +$errormsg = ''; +$focus = ''; + +$data = data_submitted(); + +// Any posted data & any action +if (!empty($data) || !empty($action)) { + require_sesskey(); +} + +if (isset($data->submitbutton)) { + $type = new stdClass(); + + if (isset($id)) { + $type->id = $id; + + lti_update_type($type, $data); + + redirect($redirect); + } else { + $type->state = LTI_TOOL_STATE_CONFIGURED; + + lti_add_type($type, $data); + + redirect($redirect); + } + +} else if (isset($data->cancel)) { + redirect($redirect); + +} else if ($action == 'accept') { + lti_set_state_for_type($id, LTI_TOOL_STATE_CONFIGURED); + redirect($redirect); + +} else if ($action == 'reject') { + lti_set_state_for_type($id, LTI_TOOL_STATE_REJECTED); + redirect($redirect); + +} else if ($action == 'delete') { + lti_delete_type($id); + redirect($redirect); +} + +// print header stuff +$PAGE->set_focuscontrol($focus); +if (empty($SITE->fullname)) { + $PAGE->set_title($settingspage->visiblename); + $PAGE->set_heading($settingspage->visiblename); + + $PAGE->navbar->add(get_string('lti_administration', 'lti'), $CFG->wwwroot.'/admin/settings.php?section=modsettinglti'); + + echo $OUTPUT->header(); + + echo $OUTPUT->box(get_string('configintrosite', 'admin')); + + if ($errormsg !== '') { + echo $OUTPUT->notification($errormsg); + + } else if ($statusmsg !== '') { + echo $OUTPUT->notification($statusmsg, 'notifysuccess'); + } + + echo '
'; + echo '
'; + echo html_writer::input_hidden_params($PAGE->url); + echo ''; + echo ''; + + echo $settingspage->output_html(); + + echo '
'; + + echo '
'; + echo '
'; + +} else { + if ($PAGE->user_allowed_editing()) { + $url = clone($PAGE->url); + if ($PAGE->user_is_editing()) { + $caption = get_string('blockseditoff'); + $url->param('adminedit', 'off'); + } else { + $caption = get_string('blocksediton'); + $url->param('adminedit', 'on'); + } + $buttons = $OUTPUT->single_button($url, $caption, 'get'); + } + + $PAGE->set_title("$SITE->shortname: " . get_string('toolsetup', 'lti')); + + $PAGE->navbar->add(get_string('lti_administration', 'lti'), $CFG->wwwroot.'/admin/settings.php?section=modsettinglti'); + + echo $OUTPUT->header(); + + if ($errormsg !== '') { + echo $OUTPUT->notification($errormsg); + + } else if ($statusmsg !== '') { + echo $OUTPUT->notification($statusmsg, 'notifysuccess'); + } + + echo $OUTPUT->heading(get_string('toolsetup', 'lti')); + echo $OUTPUT->box_start('generalbox'); + if ($action == 'add') { + $form = new mod_lti_edit_types_form(null, (object)array('isadmin' => true)); + $form->display(); + } else if ($action == 'update') { + $form = new mod_lti_edit_types_form('typessettings.php?id='.$id, (object)array('isadmin' => true)); + $type = lti_get_type_type_config($id); + $form->set_data($type); + $form->display(); + } + + echo $OUTPUT->box_end(); +} + +echo $OUTPUT->footer(); diff --git a/mod/lti/version.php b/mod/lti/version.php new file mode 100644 index 00000000000..9eb1e6d3ca8 --- /dev/null +++ b/mod/lti/version.php @@ -0,0 +1,54 @@ +. +// +// 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 defines the version of lti + * + * @package mod + * @subpackage 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; + +$module->version = 2011111400;; +$module->requires = 2011110200; // Requires this Moodle version +$module->cron = 0; +$module->component = 'mod_lti'; diff --git a/mod/lti/view.php b/mod/lti/view.php new file mode 100644 index 00000000000..0697df62d89 --- /dev/null +++ b/mod/lti/view.php @@ -0,0 +1,160 @@ +. +// +// 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 necessary code to view a lti activity instance + * + * @package mod + * @subpackage 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 + */ + +require_once('../../config.php'); +require_once($CFG->dirroot.'/mod/lti/lib.php'); +require_once($CFG->dirroot.'/mod/lti/locallib.php'); + +$id = optional_param('id', 0, PARAM_INT); // Course Module ID, or +$l = optional_param('l', 0, PARAM_INT); // lti ID + +if ($l) { // Two ways to specify the module + $lti = $DB->get_record('lti', array('id' => $l), '*', MUST_EXIST); + $cm = get_coursemodule_from_instance('lti', $lti->id, $lti->course, false, MUST_EXIST); + +} else { + $cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST); + $lti = $DB->get_record('lti', array('id' => $cm->instance), '*', MUST_EXIST); +} + +$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + +$tool = lti_get_tool_by_url_match($lti->toolurl); +if ($tool) { + $toolconfig = lti_get_type_config($tool->id); +} else { + $toolconfig = array(); +} + +$PAGE->set_cm($cm, $course); // set's up global $COURSE +$context = get_context_instance(CONTEXT_MODULE, $cm->id); +$PAGE->set_context($context); + +$url = new moodle_url('/mod/lti/view.php', array('id'=>$cm->id)); +$PAGE->set_url($url); + +$launchcontainer = lti_get_launch_container($lti, $toolconfig); + +if ($launchcontainer == LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS) { + $PAGE->set_pagelayout('frametop'); //Most frametops don't include footer, and pre-post blocks + $PAGE->blocks->show_only_fake_blocks(); //Disable blocks for layouts which do include pre-post blocks +} else if ($launchcontainer == LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW) { + redirect('launch.php?id=' . $cm->id); +} else { + $PAGE->set_pagelayout('incourse'); +} + +require_login($course); + +add_to_log($course->id, "lti", "view", "view.php?id=$cm->id", "$lti->id"); + +$pagetitle = strip_tags($course->shortname.': '.format_string($lti->name)); +$PAGE->set_title($pagetitle); +$PAGE->set_heading($course->fullname); + +// Print the page header +echo $OUTPUT->header(); + +if ($lti->showtitle) { + // Print the main part of the page + echo $OUTPUT->heading(format_string($lti->name)); +} + +if ($lti->showdescription && $lti->intro) { + echo $OUTPUT->box($lti->intro, 'generalbox description', 'intro'); +} + +if ( $launchcontainer == LTI_LAUNCH_CONTAINER_WINDOW ) { + echo "\n"; + echo "

".get_string("basiclti_in_new_window", "lti")."

\n"; +} else { + // Request the launch content with an object tag + echo ''; + + //Output script to make the object tag be as large as possible + $resize = ' + +'; + + echo $resize; +} + +// Finish the page +echo $OUTPUT->footer();