Version 3.0.0

First release of library
This commit is contained in:
Stephen Vickers 2016-05-18 23:50:06 +01:00
parent 7c7ffdfc13
commit 198f58a529
42 changed files with 10241 additions and 1 deletions

View File

@ -1 +1,17 @@
# LTI-Tool-Provider-Library-PHP
This set of PHP classes encapsulates the code required by a Learning Tools Interoperability<sup>®</sup> (LTI<sup>®</sup>) compliant tool provider to communicate with an LTI tool consumer.
It includes support for LTI 1.1 and the unofficial extensions to LTI 1.0, as well as the registration process and services of LTI 2.0.
These classes are an extension of the LTI Tool Provider class library created by the ceLTIc project (http://www.spvsoftwareproducts.com/php/lti_tool_provider/).
Whilst supporting LTI is relatively simple, the benefits to using a class library like this one are:
* the abstraction layer provided by the classes keeps the LTI communications separate from the application code;
* the code can be re-used between multiple tool providers;
* LTI data is transformed into useful objects and missing data automatically replaced with sensible defaults;
* the outcomes service function uses LTI 1.1 or the unofficial outcomes extension according to whichever is supported by the tool consumer;
* the unofficial extensions for memberships and setting services are supported;
* additional functionality is included to:
* enable/disable a consumer key;
* set start and end times for enabling access for each consumer key;
* set up arrangements such that users from different resource links can all collaborate together within a single tool provider link;
* tool providers can take advantage of LTI updates with minimal impact on their application code.
<sup><sub>Learning Tools Interoperability and LTI are registered trademarks of IMS Global Learning Consortium Inc.</sub></sup>

179
src/HTTPMessage.php Normal file
View File

@ -0,0 +1,179 @@
<?php
namespace IMSGlobal\LTI;
/**
* Class to represent an HTTP message
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class HTTPMessage
{
/**
* True if message was sent successfully.
*
* @var boolean $ok
*/
public $ok = false;
/**
* Request body.
*
* @var request $request
*/
public $request = null;
/**
* Request headers.
*
* @var request_headers $requestHeaders
*/
public $requestHeaders = '';
/**
* Response body.
*
* @var response $response
*/
public $response = null;
/**
* Response headers.
*
* @var response_headers $responseHeaders
*/
public $responseHeaders = '';
/**
* Status of response (0 if undetermined).
*
* @var status $status
*/
public $status = 0;
/**
* Error message
*
* @var error $error
*/
public $error = '';
/**
* Request URL.
*
* @var url $url
*/
private $url = null;
/**
* Request method.
*
* @var method $method
*/
private $method = null;
/**
* Class constructor.
*
* @param string $url URL to send request to
* @param string $method Request method to use (optional, default is GET)
* @param mixed $params Associative array of parameter values to be passed or message body (optional, default is none)
* @param string $header Values to include in the request header (optional, default is none)
*/
function __construct($url, $method = 'GET', $params = null, $header = null)
{
$this->url = $url;
$this->method = strtoupper($method);
if (is_array($params)) {
$this->request = http_build_query($params);
} else {
$this->request = $params;
}
if (!empty($header)) {
$this->requestHeaders = explode("\n", $header);
}
}
/**
* Send the request to the target URL.
*
* @return boolean True if the request was successful
*/
public function send()
{
$this->ok = false;
// Try using curl if available
if (function_exists('curl_init')) {
$resp = '';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
if (!empty($this->requestHeaders)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->requestHeaders);
} else {
curl_setopt($ch, CURLOPT_HEADER, 0);
}
if ($this->method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->request);
} else if ($this->method !== 'GET') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
if (!is_null($this->request)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->request);
}
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_SSLVERSION,3);
$chResp = curl_exec($ch);
$this->ok = $chResp !== false;
if ($this->ok) {
$chResp = str_replace("\r\n", "\n", $chResp);
$chRespSplit = explode("\n\n", $chResp, 2);
if ((count($chRespSplit) > 1) && (substr($chRespSplit[1], 0, 5) === 'HTTP/')) {
$chRespSplit = explode("\n\n", $chRespSplit[1], 2);
}
$this->responseHeaders = $chRespSplit[0];
$resp = $chRespSplit[1];
$this->status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$this->ok = $this->status < 400;
if (!$this->ok) {
$this->error = curl_error($ch);
}
}
$this->requestHeaders = str_replace("\r\n", "\n", curl_getinfo($ch, CURLINFO_HEADER_OUT));
curl_close($ch);
$this->response = $resp;
} else {
// Try using fopen if curl was not available
$opts = array('method' => $this->method,
'content' => $this->request
);
if (!empty($this->requestHeaders)) {
$opts['header'] = $this->requestHeaders;
}
try {
$ctx = stream_context_create(array('http' => $opts));
$fp = @fopen($this->url, 'rb', false, $ctx);
if ($fp) {
$resp = @stream_get_contents($fp);
$this->ok = $resp !== false;
}
} catch (\Exception $e) {
$this->ok = false;
}
}
return $this->ok;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Consumer
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
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]";
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Data Store
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
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, $callback = null) {
// return a new token attached to this consumer
}
function new_access_token($token, $consumer, $verifier = null) {
// 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
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Exception
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthException extends \Exception {
// pass
}

290
src/OAuth/OAuthRequest.php Normal file
View File

@ -0,0 +1,290 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Request
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthRequest {
protected $parameters;
protected $http_method;
protected $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 = ($parameters) ? $parameters : array();
$parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
$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';
$http_url = ($http_url) ? $http_url : $scheme .
'://' . $_SERVER['SERVER_NAME'] .
':' .
$_SERVER['SERVER_PORT'] .
$_SERVER['REQUEST_URI'];
$http_method = ($http_method) ? $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
if (isset($_SERVER['QUERY_STRING'])) {
$parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
} else {
$parameters = array();
}
// It's a POST request of the proper content-type, so parse POST
// parameters and add those overriding any duplicates from GET
if ($http_method == "POST"
&& isset($request_headers['Content-Type'])
&& strstr($request_headers['Content-Type'], 'application/x-www-form-urlencoded')) {
$post_data = OAuthUtil::parse_parameters(file_get_contents(self::$POST_INPUT));
$parameters = array_merge($parameters, $post_data);
}
// We have a Authorization-header with OAuth data. Parse the header
// and add those overriding any duplicates from GET or POST
if (isset($request_headers['Authorization']) && 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 = ($parameters) ? $parameters : array();
$defaults = array('oauth_version' => OAuthRequest::$version,
'oauth_nonce' => OAuthRequest::generate_nonce(),
'oauth_timestamp' => OAuthRequest::generate_timestamp(),
'oauth_consumer_key' => $consumer->key);
if ($token)
$defaults['oauth_token'] = $token->key;
$parameters = array_merge($defaults, $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);
$scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
$port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
$host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
$path = (isset($parts['path'])) ? $parts['path'] : '';
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($realm = null) {
$first = true;
if($realm) {
$out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
$first = false;
} else
$out = 'Authorization: OAuth';
$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 .= ($first) ? ' ' : ',';
$out .= OAuthUtil::urlencode_rfc3986($k) .
'="' .
OAuthUtil::urlencode_rfc3986($v) .
'"';
$first = false;
}
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
}
}

233
src/OAuth/OAuthServer.php Normal file
View File

@ -0,0 +1,233 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Server
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
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);
// Rev A change
$callback = $request->get_parameter('oauth_callback');
$new_token = $this->data_store->new_request_token($consumer, $callback);
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);
// Rev A change
$verifier = $request->get_parameter('oauth_verifier');
$new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
return $new_token;
}
/**
* verify an api call, checks all the parameters
*/
public function verify_request(&$request) {
$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) {
// Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
// Chapter 7.0 ("Accessing Protected Ressources")
$version = '1.0';
}
if ($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 instanceof OAuthRequest
? $request->get_parameter('oauth_signature_method') : NULL;
if (!$signature_method) {
// According to chapter 7 ("Accessing Protected Ressources") the signature-method
// parameter is required, and we can't just fallback to PLAINTEXT
throw new OAuthException('No signature method parameter. This parameter is required');
}
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 instanceof OAuthRequest
? $request->get_parameter('oauth_consumer_key') : NULL;
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 instanceof OAuthRequest
? $request->get_parameter('oauth_token') : NULL;
$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
$timestamp = $request instanceof OAuthRequest
? $request->get_parameter('oauth_timestamp')
: NULL;
$nonce = $request instanceof OAuthRequest
? $request->get_parameter('oauth_nonce')
: NULL;
$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) {
throw new OAuthException('Invalid signature');
}
}
/**
* check that the timestamp is new enough
*/
private function check_timestamp($timestamp) {
if(!$timestamp)
throw new OAuthException('Missing timestamp parameter. The parameter is required');
// verify that timestamp is recentish
$now = time();
if (abs($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) {
if(!$nonce)
throw new OAuthException('Missing nonce parameter. The parameter is required');
// 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");
}
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Signature Method
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
/**
* A class for implementing a Signature Method
* See section 9 ("Signing Requests") in the spec
*/
abstract class OAuthSignatureMethod {
/**
* Needs to return the name of the Signature Method (ie HMAC-SHA1)
* @return string
*/
abstract public function get_name();
/**
* Build up the signature
* NOTE: The output of this function MUST NOT be urlencoded.
* the encoding is handled in OAuthRequest when the final
* request is serialized
* @param OAuthRequest $request
* @param OAuthConsumer $consumer
* @param OAuthToken $token
* @return string
*/
abstract public function build_signature($request, $consumer, $token);
/**
* Verifies that a given signature is correct
* @param OAuthRequest $request
* @param OAuthConsumer $consumer
* @param OAuthToken $token
* @param string $signature
* @return bool
*/
public function check_signature($request, $consumer, $token, $signature) {
$built = $this->build_signature($request, $consumer, $token);
// Check for zero length, although unlikely here
if (strlen($built) == 0 || strlen($signature) == 0) {
return false;
}
if (strlen($built) != strlen($signature)) {
return false;
}
// Avoid a timing leak with a (hopefully) time insensitive compare
$result = 0;
for ($i = 0; $i < strlen($signature); $i++) {
$result |= ord($built{$i}) ^ ord($signature{$i});
}
return $result == 0;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth HMAC_SHA1 signature method
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
/**
* The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
* where the Signature Base String is the text and the key is the concatenated values (each first
* encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
* character (ASCII code 38) even if empty.
* - Chapter 9.2 ("HMAC-SHA1")
*/
class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
function get_name() {
return "HMAC-SHA1";
}
public function build_signature($request, $consumer, $token) {
$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);
return base64_encode(hash_hmac('sha1', $base_string, $key, true));
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth HMAC_SHA256 signature method
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 2015-11-30
* @license https://opensource.org/licenses/MIT The MIT License
*/
/**
* The HMAC-SHA256 signature method uses the HMAC-SHA256 signature algorithm as defined in [RFC6234]
* where the Signature Base String is the text and the key is the concatenated values (each first
* encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
* character (ASCII code 38) even if empty.
*/
class OAuthSignatureMethod_HMAC_SHA256 extends OAuthSignatureMethod {
function get_name() {
return "HMAC-SHA256";
}
public function build_signature($request, $consumer, $token) {
$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);
return base64_encode(hash_hmac('sha256', $base_string, $key, true));
}
}

42
src/OAuth/OAuthToken.php Normal file
View File

@ -0,0 +1,42 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Token
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
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();
}
}

161
src/OAuth/OAuthUtil.php Normal file
View File

@ -0,0 +1,161 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to provide %OAuth utility methods
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthUtil {
public static function urlencode_rfc3986($input) {
if (is_array($input)) {
return array_map(array('IMSGlobal\LTI\OAuth\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)
// May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
// see http://code.google.com/p/oauth/issues/detail?id=163
public static function split_header($header, $only_allow_oauth_parameters = true) {
$params = array();
if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
foreach ($matches[1] as $i => $h) {
$params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
}
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
$headers = apache_request_headers();
// sanitize the output of apache_request_headers because
// we always want the keys to be Cased-Like-This and arh()
// returns the headers in the same case as they are in the
// request
$out = array();
foreach ($headers AS $key => $value) {
$key = str_replace(" ", "-", ucwords(strtolower(str_replace("-", " ", $key))));
$out[$key] = $value;
}
} else {
// otherwise we don't have apache and are just going to have to hope
// that $_SERVER actually contains what we need
$out = array();
if( isset($_SERVER['CONTENT_TYPE']) )
$out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
if( isset($_ENV['CONTENT_TYPE']) )
$out['Content-Type'] = $_ENV['CONTENT_TYPE'];
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 = OAuthUtil::urlencode_rfc3986(array_keys($params));
$values = OAuthUtil::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)
// June 12th, 2010 - changed to sort because of issue 164 by hidetaka
sort($value, SORT_STRING);
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);
}
}

77
src/Profile/Item.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace IMSGlobal\LTI\Profile;
/**
* Class to represent a generic item object
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class Item
{
/**
* ID of item.
*
* @var string $id
*/
public $id = null;
/**
* Name of item.
*
* @var string $name
*/
public $name = null;
/**
* Description of item.
*
* @var string $description
*/
public $description = null;
/**
* URL of item.
*
* @var string $url
*/
public $url = null;
/**
* Version of item.
*
* @var string $version
*/
public $version = null;
/**
* Timestamp of item.
*
* @var int $timestamp
*/
public $timestamp = null;
/**
* Class constructor.
*
* @param string $id ID of item (optional)
* @param string $name Name of item (optional)
* @param string $description Description of item (optional)
* @param string $url URL of item (optional)
* @param string $version Version of item (optional)
* @param int $timestamp Timestamp of item (optional)
*/
function __construct($id = null, $name = null, $description = null, $url = null, $version = null, $timestamp = null)
{
$this->id = $id;
$this->name = $name;
$this->description = $description;
$this->url = $url;
$this->version = $version;
$this->timestamp = $timestamp;
}
}

71
src/Profile/Message.php Normal file
View File

@ -0,0 +1,71 @@
<?php
namespace IMSGlobal\LTI\Profile;
/**
* Class to represent a resource handler message object
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class Message
{
/**
* LTI message type.
*
* @var string $type
*/
public $type = null;
/**
* Path to send message request to (used in conjunction with a base URL for the Tool Provider).
*
* @var string $path
*/
public $path = null;
/**
* Capabilities required by message.
*
* @var array $capabilities
*/
public $capabilities = null;
/**
* Variable parameters to accompany message request.
*
* @var array $variables
*/
public $variables = null;
/**
* Fixed parameters to accompany message request.
*
* @var array $constants
*/
public $constants = null;
/**
* Class constructor.
*
* @param string $type LTI message type
* @param string $path Path to send message request to
* @param array $capabilities Array of capabilities required by message
* @param array $variables Array of variable parameters to accompany message request
* @param array $constants Array of fixed parameters to accompany message request
*/
function __construct($type, $path, $capabilities = array(), $variables = array(), $constants = array())
{
$this->type = $type;
$this->path = $path;
$this->capabilities = $capabilities;
$this->variables = $variables;
$this->constants = $constants;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace IMSGlobal\LTI\Profile;
/**
* Class to represent a resource handler object
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ResourceHandler
{
/**
* General details of resource handler.
*
* @var Item $item
*/
public $item = null;
/**
* URL of icon.
*
* @var string $icon
*/
public $icon = null;
/**
* Required Message objects for resource handler.
*
* @var array $requiredMessages
*/
public $requiredMessages = null;
/**
* Optional Message objects for resource handler.
*
* @var array $optionalMessages
*/
public $optionalMessages = null;
/**
* Class constructor.
*
* @param Item $item General details of resource handler
* @param string $icon URL of icon
* @param array $requiredMessages Array of required Message objects for resource handler
* @param array $optionalMessages Array of optional Message objects for resource handler
*/
function __construct($item, $icon, $requiredMessages, $optionalMessages)
{
$this->item = $item;
$this->icon = $icon;
$this->requiredMessages = $requiredMessages;
$this->optionalMessages = $optionalMessages;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace IMSGlobal\LTI\Profile;
/**
* Class to represent an LTI service object
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ServiceDefinition
{
/**
* Media types supported by service.
*
* @var array $formats
*/
public $formats = null;
/**
* HTTP actions accepted by service.
*
* @var array $actions
*/
public $actions = null;
/**
* ID of service.
*
* @var string $id
*/
public $id = null;
/**
* URL for service requests.
*
* @var string $endpoint
*/
public $endpoint = null;
/**
* Class constructor.
*
* @param array $formats Array of media types supported by service
* @param array $actions Array of HTTP actions accepted by service
* @param string $id ID of service (optional)
* @param string $endpoint URL for service requests (optional)
*/
function __construct($formats, $actions, $id = null, $endpoint = null)
{
$this->formats = $formats;
$this->actions = $actions;
$this->id = $id;
$this->endpoint = $endpoint;
}
function setId($id) {
$this->id = $id;
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent a tool consumer nonce
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ConsumerNonce
{
/**
* Maximum age nonce values will be retained for (in minutes).
*/
const MAX_NONCE_AGE = 30; // in minutes
/**
* Date/time when the nonce value expires.
*
* @var datetime $expires
*/
public $expires = null;
/**
* Tool Consumer to which this nonce applies.
*
* @var ToolConsumer $consumer
*/
private $consumer = null;
/**
* Nonce value.
*
* @var string $value
*/
private $value = null;
/**
* Class constructor.
*
* @param ToolConsumer $consumer Consumer object
* @param string $value Nonce value (optional, default is null)
*/
public function __construct($consumer, $value = null)
{
$this->consumer = $consumer;
$this->value = $value;
$this->expires = time() + (self::MAX_NONCE_AGE * 60);
}
/**
* Load a nonce value from the database.
*
* @return boolean True if the nonce value was successfully loaded
*/
public function load()
{
return $this->consumer->getDataConnector()->loadConsumerNonce($this);
}
/**
* Save a nonce value in the database.
*
* @return boolean True if the nonce value was successfully saved
*/
public function save()
{
return $this->consumer->getDataConnector()->saveConsumerNonce($this);
}
/**
* Get tool consumer.
*
* @return ToolConsumer Consumer for this nonce
*/
public function getConsumer()
{
return $this->consumer;
}
/**
* Get outcome value.
*
* @return string Outcome value
*/
public function getValue()
{
return $this->value;
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent a content-item object
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ContentItem
{
/**
* Media type for LTI launch links.
*/
const LTI_LINK_MEDIA_TYPE = 'application/vnd.ims.lti.v1.ltilink';
/**
* Class constructor.
*
* @param string $type Class type of content-item
* @param ContentItemPlacement $placementAdvice Placement object for item (optional)
* @param string $id URL of content-item (optional)
*/
function __construct($type, $placementAdvice = null, $id = null)
{
$this->{'@type'} = $type;
if (is_object($placementAdvice) && (count(get_object_vars($placementAdvice)) > 0)) {
$this->placementAdvice = $placementAdvice;
}
if (!empty($id)) {
$this->{'@id'} = $id;
}
}
/**
* Set a URL value for the content-item.
*
* @param string $url URL value
*/
public function setUrl($url)
{
if (!empty($url)) {
$this->url = $url;
} else {
unset($this->url);
}
}
/**
* Set a media type value for the content-item.
*
* @param string $mediaType Media type value
*/
public function setMediaType($mediaType)
{
if (!empty($mediaType)) {
$this->mediaType = $mediaType;
} else {
unset($this->mediaType);
}
}
/**
* Set a title value for the content-item.
*
* @param string $title Title value
*/
public function setTitle($title)
{
if (!empty($title)) {
$this->title = $title;
} else if (isset($this->title)) {
unset($this->title);
}
}
/**
* Set a link text value for the content-item.
*
* @param string $text Link text value
*/
public function setText($text)
{
if (!empty($text)) {
$this->text = $text;
} else if (isset($this->text)) {
unset($this->text);
}
}
/**
* Wrap the content items to form a complete application/vnd.ims.lti.v1.contentitems+json media type instance.
*
* @param mixed $items An array of content items or a single item
*/
public static function toJson($items)
{
/*
$data = array();
if (!is_array($items)) {
$data[] = json_encode($items);
} else {
foreach ($items as $item) {
$data[] = json_encode($item);
}
}
$json = '{ "@context" : "http://purl.imsglobal.org/ctx/lti/v1/ContentItem", "@graph" : [' . implode(", ", $data) . '] }';
*/
$obj = new \stdClass();
$obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem';
if (!is_array($items)) {
$obj->{'@graph'} = array();
$obj->{'@graph'}[] = $items;
} else {
$obj->{'@graph'} = $items;
}
return json_encode($obj);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent a content-item image object
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ContentItemImage
{
/**
* Class constructor.
*
* @param string $id URL of image
* @param int $height Height of image in pixels (optional)
* @param int $width Width of image in pixels (optional)
*/
function __construct($id, $height = null, $width = null)
{
$this->{'@id'} = $id;
if (!is_null($height)) {
$this->height = $height;
}
if (!is_null($width)) {
$this->width = $width;
}
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent a content-item placement object
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ContentItemPlacement
{
/**
* Class constructor.
*
* @param int $displayWidth Width of item location
* @param int $displayHeight Height of item location
* @param string $documentTarget Location to open content in
* @param string $windowTarget Name of window target
*/
function __construct($displayWidth, $displayHeight, $documentTarget, $windowTarget)
{
if (!empty($displayWidth)) {
$this->displayWidth = $displayWidth;
}
if (!empty($displayHeight)) {
$this->displayHeight = $displayHeight;
}
if (!empty($documentTarget)) {
$this->documentTarget = $documentTarget;
}
if (!empty($windowTarget)) {
$this->windowTarget = $windowTarget;
}
}
}

View File

@ -0,0 +1,446 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\Service;
/**
* Class to represent a tool consumer context
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class Context
{
/**
* Context ID as supplied in the last connection request.
*
* @var string $ltiContextId
*/
public $ltiContextId = null;
/**
* Context title.
*
* @var string $title
*/
public $title = null;
/**
* Setting values (LTI parameters, custom parameters and local parameters).
*
* @var array $settings
*/
public $settings = null;
/**
* Date/time when the object was created.
*
* @var datetime $created
*/
public $created = null;
/**
* Date/time when the object was last updated.
*
* @var datetime $updated
*/
public $updated = null;
/**
* Tool Consumer for this context.
*
* @var ToolConsumer $consumer
*/
private $consumer = null;
/**
* Tool Consumer ID for this context.
*
* @var int $consumerId
*/
private $consumerId = null;
/**
* ID for this context.
*
* @var int $id
*/
private $id = null;
/**
* Whether the settings value have changed since last saved.
*
* @var boolean $settingsChanged
*/
private $settingsChanged = false;
/**
* Data connector object or string.
*
* @var mixed $dataConnector
*/
private $dataConnector = null;
/**
* Class constructor.
*/
public function __construct()
{
$this->initialize();
}
/**
* Initialise the context.
*/
public function initialize()
{
$this->title = '';
$this->settings = array();
$this->created = null;
$this->updated = null;
}
/**
* Initialise the context.
*
* Pseudonym for initialize().
*/
public function initialise()
{
$this->initialize();
}
/**
* Save the context to the database.
*
* @return boolean True if the context was successfully saved.
*/
public function save()
{
$ok = $this->getDataConnector()->saveContext($this);
if ($ok) {
$this->settingsChanged = false;
}
return $ok;
}
/**
* Delete the context from the database.
*
* @return boolean True if the context was successfully deleted.
*/
public function delete()
{
return $this->getDataConnector()->deleteContext($this);
}
/**
* Get tool consumer.
*
* @return ToolConsumer Tool consumer object for this context.
*/
public function getConsumer()
{
if (is_null($this->consumer)) {
$this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector());
}
return $this->consumer;
}
/**
* Set tool consumer ID.
*
* @param int $consumerId Tool Consumer ID for this resource link.
*/
public function setConsumerId($consumerId)
{
$this->consumer = null;
$this->consumerId = $consumerId;
}
/**
* Get tool consumer key.
*
* @return string Consumer key value for this context.
*/
public function getKey()
{
return $this->getConsumer()->getKey();
}
/**
* Get the context record ID.
*
* @return int Context record ID value
*/
public function getRecordId()
{
return $this->id;
}
/**
* Sets the context record ID.
*
* @return int $id Context record ID value
*/
public function setRecordId($id)
{
$this->id = $id;
}
/**
* Get the data connector.
*
* @return mixed Data connector object or string
*/
public function getDataConnector()
{
return $this->dataConnector;
}
/**
* Get a setting value.
*
* @param string $name Name of setting
* @param string $default Value to return if the setting does not exist (optional, default is an empty string)
*
* @return string Setting value
*/
public function getSetting($name, $default = '')
{
if (array_key_exists($name, $this->settings)) {
$value = $this->settings[$name];
} else {
$value = $default;
}
return $value;
}
/**
* Set a setting value.
*
* @param string $name Name of setting
* @param string $value Value to set, use an empty value to delete a setting (optional, default is null)
*/
public function setSetting($name, $value = null)
{
$old_value = $this->getSetting($name);
if ($value !== $old_value) {
if (!empty($value)) {
$this->settings[$name] = $value;
} else {
unset($this->settings[$name]);
}
$this->settingsChanged = true;
}
}
/**
* Get an array of all setting values.
*
* @return array Associative array of setting values
*/
public function getSettings()
{
return $this->settings;
}
/**
* Set an array of all setting values.
*
* @param array $settings Associative array of setting values
*/
public function setSettings($settings)
{
$this->settings = $settings;
}
/**
* Save setting values.
*
* @return boolean True if the settings were successfully saved
*/
public function saveSettings()
{
if ($this->settingsChanged) {
$ok = $this->save();
} else {
$ok = true;
}
return $ok;
}
/**
* Check if the Tool Settings service is supported.
*
* @return boolean True if this context supports the Tool Settings service
*/
public function hasToolSettingsService()
{
$url = $this->getSetting('custom_context_setting_url');
return !empty($url);
}
/**
* Get Tool Settings.
*
* @param int $mode Mode for request (optional, default is current level only)
* @param boolean $simple True if all the simple media type is to be used (optional, default is true)
*
* @return mixed The array of settings if successful, otherwise false
*/
public function getToolSettings($mode = Service\ToolSettings::MODE_CURRENT_LEVEL, $simple = true)
{
$url = $this->getSetting('custom_context_setting_url');
$service = new Service\ToolSettings($this, $url, $simple);
$response = $service->get($mode);
return $response;
}
/**
* Perform a Tool Settings service request.
*
* @param array $settings An associative array of settings (optional, default is none)
*
* @return boolean True if action was successful, otherwise false
*/
public function setToolSettings($settings = array())
{
$url = $this->getSetting('custom_context_setting_url');
$service = new Service\ToolSettings($this, $url);
$response = $service->set($settings);
return $response;
}
/**
* Check if the Membership service is supported.
*
* @return boolean True if this context supports the Membership service
*/
public function hasMembershipService()
{
$url = $this->getSetting('custom_context_memberships_url');
return !empty($url);
}
/**
* Get Memberships.
*
* @return mixed The array of User objects if successful, otherwise false
*/
public function getMembership()
{
$url = $this->getSetting('custom_context_memberships_url');
$service = new Service\Membership($this, $url);
$response = $service->get();
return $response;
}
/**
* Load the context from the database.
*
* @param int $id Record ID of context
* @param DataConnector $dataConnector Database connection object
*
* @return Context Context object
*/
public static function fromRecordId($id, $dataConnector)
{
$context = new Context();
$context->dataConnector = $dataConnector;
$context->load($id);
return $context;
}
/**
* Class constructor from consumer.
*
* @param LTIToolConsumer $consumer Consumer instance
* @param string $ltiContextId LTI Context ID value
*/
public static function fromConsumer($consumer, $ltiContextId)
{
$context = new Context();
$context->consumer = $consumer;
$context->dataConnector = $consumer->getDataConnector();
$context->ltiContextId = $ltiContextId;
if (!empty($ltiContextId)) {
$context->load();
}
return $context;
}
###
### PRIVATE METHODS
###
/**
* Load the context from the database.
*
* @param int $id Record ID of context (optional, default is null)
*
* @return boolean True if context was successfully loaded
*/
private function load($id = null)
{
$this->initialize();
$this->id = $id;
return $this->getDataConnector()->loadContext($this);
}
}

View File

@ -0,0 +1,601 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\DataConnector;
use PDO;
/**
* Class to provide a connection to a persistent store for LTI objects
*
* This class assumes no data persistence - it should be extended for specific database connections.
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class DataConnector
{
/**
* Default name for database table used to store tool consumers.
*/
const CONSUMER_TABLE_NAME = 'lti2_consumer';
/**
* Default name for database table used to store pending tool proxies.
*/
const TOOL_PROXY_TABLE_NAME = 'lti2_tool_proxy';
/**
* Default name for database table used to store contexts.
*/
const CONTEXT_TABLE_NAME = 'lti2_context';
/**
* Default name for database table used to store resource links.
*/
const RESOURCE_LINK_TABLE_NAME = 'lti2_resource_link';
/**
* Default name for database table used to store users.
*/
const USER_RESULT_TABLE_NAME = 'lti2_user_result';
/**
* Default name for database table used to store resource link share keys.
*/
const RESOURCE_LINK_SHARE_KEY_TABLE_NAME = 'lti2_share_key';
/**
* Default name for database table used to store nonce values.
*/
const NONCE_TABLE_NAME = 'lti2_nonce';
/**
* Database object.
*
* @var object $db
*/
protected $db = null;
/**
* Prefix for database table names.
*
* @var string $dbTableNamePrefix
*/
protected $dbTableNamePrefix = '';
/**
* SQL date format (default = 'Y-m-d')
*
* @var string $dateFormat
*/
protected $dateFormat = 'Y-m-d';
/**
* SQL time format (default = 'H:i:s')
*
* @var string $timeFormat
*/
protected $timeFormat = 'H:i:s';
/**
* Class constructor
*
* @param object $db Database connection object
* @param string $dbTableNamePrefix Prefix for database table names (optional, default is none)
*/
public function __construct($db, $dbTableNamePrefix = '')
{
$this->db = $db;
$this->dbTableNamePrefix = $dbTableNamePrefix;
}
###
### ToolConsumer methods
###
/**
* Load tool consumer object.
*
* @param ToolConsumer $consumer ToolConsumer object
*
* @return boolean True if the tool consumer object was successfully loaded
*/
public function loadToolConsumer($consumer)
{
$consumer->secret = 'secret';
$consumer->enabled = true;
$now = time();
$consumer->created = $now;
$consumer->updated = $now;
return true;
}
/**
* Save tool consumer object.
*
* @param ToolConsumer $consumer Consumer object
*
* @return boolean True if the tool consumer object was successfully saved
*/
public function saveToolConsumer($consumer)
{
$consumer->updated = time();
return true;
}
/**
* Delete tool consumer object.
*
* @param ToolConsumer $consumer Consumer object
*
* @return boolean True if the tool consumer object was successfully deleted
*/
public function deleteToolConsumer($consumer)
{
$consumer->initialize();
return true;
}
/**
* Load tool consumer objects.
*
* @return array Array of all defined ToolConsumer objects
*/
public function getToolConsumers()
{
return array();
}
###
### ToolProxy methods
###
/**
* Load tool proxy object.
*
* @param ToolProxy $toolProxy ToolProxy object
*
* @return boolean True if the tool proxy object was successfully loaded
*/
public function loadToolProxy($toolProxy)
{
$now = time();
$toolProxy->created = $now;
$toolProxy->updated = $now;
return true;
}
/**
* Save tool proxy object.
*
* @param ToolProxy $toolProxy ToolProxy object
*
* @return boolean True if the tool proxy object was successfully saved
*/
public function saveToolProxy($toolProxy)
{
$toolProxy->updated = time();
return true;
}
/**
* Delete tool proxy object.
*
* @param ToolProxy $toolProxy ToolProxy object
*
* @return boolean True if the tool proxy object was successfully deleted
*/
public function deleteToolProxy($toolProxy)
{
$toolProxy->initialize();
return true;
}
###
### Context methods
###
/**
* Load context object.
*
* @param Context $context Context object
*
* @return boolean True if the context object was successfully loaded
*/
public function loadContext($context)
{
$now = time();
$context->created = $now;
$context->updated = $now;
return true;
}
/**
* Save context object.
*
* @param Context $context Context object
*
* @return boolean True if the context object was successfully saved
*/
public function saveContext($context)
{
$context->updated = time();
return true;
}
/**
* Delete context object.
*
* @param Context $context Context object
*
* @return boolean True if the Context object was successfully deleted
*/
public function deleteContext($context)
{
$context->initialize();
return true;
}
###
### ResourceLink methods
###
/**
* Load resource link object.
*
* @param ResourceLink $resourceLink Resource_Link object
*
* @return boolean True if the resource link object was successfully loaded
*/
public function loadResourceLink($resourceLink)
{
$now = time();
$resourceLink->created = $now;
$resourceLink->updated = $now;
return true;
}
/**
* Save resource link object.
*
* @param ResourceLink $resourceLink Resource_Link object
*
* @return boolean True if the resource link object was successfully saved
*/
public function saveResourceLink($resourceLink)
{
$resourceLink->updated = time();
return true;
}
/**
* Delete resource link object.
*
* @param ResourceLink $resourceLink Resource_Link object
*
* @return boolean True if the resource link object was successfully deleted
*/
public function deleteResourceLink($resourceLink)
{
$resourceLink->initialize();
return true;
}
/**
* Get array of user objects.
*
* Obtain an array of User objects for users with a result sourcedId. The array may include users from other
* resource links which are sharing this resource link. It may also be optionally indexed by the user ID of a specified scope.
*
* @param ResourceLink $resourceLink Resource link object
* @param boolean $localOnly True if only users within the resource link are to be returned (excluding users sharing this resource link)
* @param int $idScope Scope value to use for user IDs
*
* @return array Array of User objects
*/
public function getUserResultSourcedIDsResourceLink($resourceLink, $localOnly, $idScope)
{
return array();
}
/**
* Get array of shares defined for this resource link.
*
* @param ResourceLink $resourceLink Resource_Link object
*
* @return array Array of ResourceLinkShare objects
*/
public function getSharesResourceLink($resourceLink)
{
return array();
}
###
### ConsumerNonce methods
###
/**
* Load nonce object.
*
* @param ConsumerNonce $nonce Nonce object
*
* @return boolean True if the nonce object was successfully loaded
*/
public function loadConsumerNonce($nonce)
{
return false; // assume the nonce does not already exist
}
/**
* Save nonce object.
*
* @param ConsumerNonce $nonce Nonce object
*
* @return boolean True if the nonce object was successfully saved
*/
public function saveConsumerNonce($nonce)
{
return true;
}
###
### ResourceLinkShareKey methods
###
/**
* Load resource link share key object.
*
* @param ResourceLinkShareKey $shareKey Resource_Link share key object
*
* @return boolean True if the resource link share key object was successfully loaded
*/
public function loadResourceLinkShareKey($shareKey)
{
return true;
}
/**
* Save resource link share key object.
*
* @param ResourceLinkShareKey $shareKey Resource link share key object
*
* @return boolean True if the resource link share key object was successfully saved
*/
public function saveResourceLinkShareKey($shareKey)
{
return true;
}
/**
* Delete resource link share key object.
*
* @param ResourceLinkShareKey $shareKey Resource link share key object
*
* @return boolean True if the resource link share key object was successfully deleted
*/
public function deleteResourceLinkShareKey($shareKey)
{
return true;
}
###
### User methods
###
/**
* Load user object.
*
* @param User $user User object
*
* @return boolean True if the user object was successfully loaded
*/
public function loadUser($user)
{
$now = time();
$user->created = $now;
$user->updated = $now;
return true;
}
/**
* Save user object.
*
* @param User $user User object
*
* @return boolean True if the user object was successfully saved
*/
public function saveUser($user)
{
$user->updated = time();
return true;
}
/**
* Delete user object.
*
* @param User $user User object
*
* @return boolean True if the user object was successfully deleted
*/
public function deleteUser($user)
{
$user->initialize();
return true;
}
###
### Other methods
###
/**
* Return a hash of a consumer key for values longer than 255 characters.
*
* @param string $key
* @return string
*/
protected static function getConsumerKey($key)
{
$len = strlen($key);
if ($len > 255) {
$key = 'sha512:' . hash('sha512', $key);
}
return $key;
}
/**
* Create data connector object.
*
* A data connector provides access to persistent storage for the different objects.
*
* Names of tables may be given a prefix to allow multiple versions to share the same schema. A separate sub-class is defined for
* each different database connection - the class to use is determined by inspecting the database object passed, but this can be overridden
* (for example, to use a bespoke connector) by specifying a type. If no database is passed then this class is used which acts as a dummy
* connector with no persistence.
*
* @param string $dbTableNamePrefix Prefix for database table names (optional, default is none)
* @param object $db A database connection object or string (optional, default is no persistence)
* @param string $type The type of data connector (optional, default is based on $db parameter)
*
* @return DataConnector Data connector object
*/
public static function getDataConnector($dbTableNamePrefix = '', $db = null, $type = '')
{
if (is_null($dbTableNamePrefix)) {
$dbTableNamePrefix = '';
}
if (!is_null($db) && empty($type)) {
if (is_object($db)) {
$type = get_class($db);
}
}
$type = strtolower($type);
if (($type === 'pdo') && ($db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite')) {
$type .= '_sqlite';
}
if (!empty($type)) {
$type ="DataConnector_{$type}";
} else {
$type ='DataConnector';
}
$type = "\\IMSGlobal\\LTI\\ToolProvider\\DataConnector\\{$type}";
$dataConnector = new $type($db, $dbTableNamePrefix);
return $dataConnector;
}
/**
* Generate a random string.
*
* The generated string will only comprise letters (upper- and lower-case) and digits.
*
* @param int $length Length of string to be generated (optional, default is 8 characters)
*
* @return string Random string
*/
static function getRandomString($length = 8)
{
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$value = '';
$charsLength = strlen($chars) - 1;
for ($i = 1 ; $i <= $length; $i++) {
$value .= $chars[rand(0, $charsLength)];
}
return $value;
}
/**
* Quote a string for use in a database query.
*
* Any single quotes in the value passed will be replaced with two single quotes. If a null value is passed, a string
* of 'null' is returned (which will never be enclosed in quotes irrespective of the value of the $addQuotes parameter.
*
* @param string $value Value to be quoted
* @param string $addQuotes If true the returned string will be enclosed in single quotes (optional, default is true)
*
* @return boolean True if the user object was successfully deleted
*/
static function quoted($value, $addQuotes = true)
{
if (is_null($value)) {
$value = 'null';
} else {
$value = str_replace('\'', '\'\'', $value);
if ($addQuotes) {
$value = "'{$value}'";
}
}
return $value;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,197 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\DataConnector;
use IMSGlobal\LTI\ToolProvider;
use PDO;
/**
* Class to represent an LTI Data Connector for PDO variations for SQLite connections
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class DataConnector_pdo_sqlite extends DataConnector_pdo
{
###
### ToolConsumer methods
###
/**
* Delete tool consumer object.
*
* @param ToolConsumer $consumer Consumer object
*
* @return boolean True if the tool consumer object was successfully deleted
*/
public function deleteToolConsumer($consumer)
{
$id = $consumer->getRecordId();
// Delete any nonce values for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE consumer_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any outstanding share keys for resource links for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any outstanding share keys for resource links for contexts in this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any users in resource links for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any users in resource links for contexts in this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Update any resource links for which this consumer is acting as a primary resource link
$sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'SET primary_resource_link_pk = NULL, share_approved = NULL ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Update any resource links for contexts in which this consumer is acting as a primary resource link
$sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'SET primary_resource_link_pk = NULL, share_approved = NULL ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any resource links for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'WHERE consumer_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any resource links for contexts in this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.context_pk = c.context_pk) AND (c.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any contexts for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' .
'WHERE consumer_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' .
'WHERE consumer_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$ok = $query->execute();
if ($ok) {
$consumer->initialize();
}
return $ok;
}
###
### Context methods
###
/**
* Delete context object.
*
* @param Context $context Context object
*
* @return boolean True if the Context object was successfully deleted
*/
public function deleteContext($context)
{
$id = $context->getRecordId();
// Delete any outstanding share keys for resource links for this context
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any users in resource links for this context
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Update any resource links for which this consumer is acting as a primary resource link
$sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'SET primary_resource_link_pk = null, share_approved = null ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any resource links for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'WHERE context_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete context
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' .
'WHERE context_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$ok = $query->execute();
if ($ok) {
$context->initialize();
}
return $ok;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\MediaType;
/**
* Class to represent an LTI Message
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license GNU Lesser General Public License, version 3 (<http://www.gnu.org/licenses/lgpl.html>)
*/
class Message
{
/**
* Class constructor.
*
* @param Message $message Message object
* @param array $capabilitiesOffered Capabilities offered
*/
function __construct($message, $capabilitiesOffered)
{
$this->message_type = $message->type;
$this->path = $message->path;
$this->enabled_capability = array();
foreach ($message->capabilities as $capability) {
if (in_array($capability, $capabilitiesOffered)) {
$this->enabled_capability[] = $capability;
}
}
$this->parameter = array();
foreach ($message->constants as $name => $value) {
$parameter = new \stdClass;
$parameter->name = $name;
$parameter->fixed = $value;
$this->parameter[] = $parameter;
}
foreach ($message->variables as $name => $value) {
if (in_array($value, $capabilitiesOffered)) {
$parameter = new \stdClass;
$parameter->name = $name;
$parameter->variable = $value;
$this->parameter[] = $parameter;
}
}
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\MediaType;
/**
* Class to represent an LTI Resource Handler
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license GNU Lesser General Public License, version 3 (<http://www.gnu.org/licenses/lgpl.html>)
*/
class ResourceHandler
{
/**
* Class constructor.
*
* @param ToolProvider $toolProvider Tool Provider object
* @param resourceHandler $resourceHandler Resource handler object
*/
function __construct($toolProvider, $resourceHandler)
{
$this->resource_type = new \stdClass;
$this->resource_type->code = $resourceHandler->item->id;
$this->resource_name = new \stdClass;
$this->resource_name->default_value = $resourceHandler->item->name;
$this->resource_name->key = "{$resourceHandler->item->id}.resource.name";
$this->description = new \stdClass;
$this->description->default_value = $resourceHandler->item->description;
$this->description->key = "{$resourceHandler->item->id}.resource.description";
$this->icon_info = new \stdClass;
$this->icon_info->default_location = new \stdClass;
$this->icon_info->default_location->path = $resourceHandler->icon;
$this->icon_info->key = "{$resourceHandler->item->id}.icon.path";
$this->message = array();
foreach ($resourceHandler->requiredMessages as $message) {
$this->message[] = new Message($message, $toolProvider->consumer->profile->capability_offered);
}
foreach ($resourceHandler->optionalMessages as $message) {
if (in_array($message->type, $toolProvider->consumer->profile->capability_offered)) {
$this->message[] = new Message($message, $toolProvider->consumer->profile->capability_offered);
}
}
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\MediaType;
/**
* Class to represent an LTI Security Contract document
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license GNU Lesser General Public License, version 3 (<http://www.gnu.org/licenses/lgpl.html>)
*/
class SecurityContract
{
/**
* Class constructor.
*
* @param ToolProvider $toolProvider Tool Provider instance
* @param string $secret Shared secret
*/
function __construct($toolProvider, $secret)
{
$tcContexts = array();
foreach ($toolProvider->consumer->profile->{'@context'} as $context) {
if (is_object($context)) {
$tcContexts = array_merge(get_object_vars($context), $tcContexts);
}
}
$this->shared_secret = $secret;
$toolServices = array();
foreach ($toolProvider->requiredServices as $requiredService) {
foreach ($requiredService->formats as $format) {
$service = $toolProvider->findService($format, $requiredService->actions);
if (($service !== false) && !array_key_exists($service->{'@id'}, $toolServices)) {
$id = $service->{'@id'};
$parts = explode(':', $id, 2);
if (count($parts) > 1) {
if (array_key_exists($parts[0], $tcContexts)) {
$id = "{$tcContexts[$parts[0]]}{$parts[1]}";
}
}
$toolService = new \stdClass;
$toolService->{'@type'} = 'RestServiceProfile';
$toolService->service = $id;
$toolService->action = $requiredService->actions;
$toolServices[$service->{'@id'}] = $toolService;
}
}
}
foreach ($toolProvider->optionalServices as $optionalService) {
foreach ($optionalService->formats as $format) {
$service = $toolProvider->findService($format, $optionalService->actions);
if (($service !== false) && !array_key_exists($service->{'@id'}, $toolServices)) {
$id = $service->{'@id'};
$parts = explode(':', $id, 2);
if (count($parts) > 1) {
if (array_key_exists($parts[0], $tcContexts)) {
$id = "{$tcContexts[$parts[0]]}{$parts[1]}";
}
}
$toolService = new \stdClass;
$toolService->{'@type'} = 'RestServiceProfile';
$toolService->service = $id;
$toolService->action = $optionalService->actions;
$toolServices[$service->{'@id'}] = $toolService;
}
}
}
$this->tool_service = array_values($toolServices);
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\MediaType;
/**
* Class to represent an LTI Tool Profile
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license GNU Lesser General Public License, version 3 (<http://www.gnu.org/licenses/lgpl.html>)
*/
class ToolProfile
{
public $product_instance;
/**
* Class constructor.
*
* @param ToolProvider $toolProvider Tool Provider object
*/
function __construct($toolProvider)
{
$this->lti_version = 'LTI-2p0';
if (!empty($toolProvider->product)) {
$this->product_instance = new \stdClass;
}
if (!empty($toolProvider->product->id)) {
$this->product_instance->guid = $toolProvider->product->id;
}
if (!empty($toolProvider->product->name)) {
$this->product_instance->product_info = new \stdClass;
$this->product_instance->product_info->product_name = new \stdClass;
$this->product_instance->product_info->product_name->default_value = $toolProvider->product->name;
$this->product_instance->product_info->product_name->key = 'tool.name';
}
if (!empty($toolProvider->product->description)) {
$this->product_instance->product_info->description = new \stdClass;
$this->product_instance->product_info->description->default_value = $toolProvider->product->description;
$this->product_instance->product_info->description->key = 'tool.description';
}
if (!empty($toolProvider->product->url)) {
$this->product_instance->guid = $toolProvider->product->url;
}
if (!empty($toolProvider->product->version)) {
$this->product_instance->product_info->product_version = $toolProvider->product->version;
}
if (!empty($toolProvider->vendor)) {
$this->product_instance->product_info->product_family = new \stdClass;
$this->product_instance->product_info->product_family->vendor = new \stdClass;
}
if (!empty($toolProvider->vendor->id)) {
$this->product_instance->product_info->product_family->vendor->code = $toolProvider->vendor->id;
}
if (!empty($toolProvider->vendor->name)) {
$this->product_instance->product_info->product_family->vendor->vendor_name = new \stdClass;
$this->product_instance->product_info->product_family->vendor->vendor_name->default_value = $toolProvider->vendor->name;
$this->product_instance->product_info->product_family->vendor->vendor_name->key = 'tool.vendor.name';
}
if (!empty($toolProvider->vendor->description)) {
$this->product_instance->product_info->product_family->vendor->description = new \stdClass;
$this->product_instance->product_info->product_family->vendor->description->default_value = $toolProvider->vendor->description;
$this->product_instance->product_info->product_family->vendor->description->key = 'tool.vendor.description';
}
if (!empty($toolProvider->vendor->url)) {
$this->product_instance->product_info->product_family->vendor->website = $toolProvider->vendor->url;
}
if (!empty($toolProvider->vendor->timestamp)) {
$this->product_instance->product_info->product_family->vendor->timestamp = date('Y-m-d\TH:i:sP', $toolProvider->vendor->timestamp);
}
$this->resource_handler = array();
foreach ($toolProvider->resourceHandlers as $resourceHandler) {
$this->resource_handler[] = new ResourceHandler($toolProvider, $resourceHandler);
}
if (!empty($toolProvider->baseUrl)) {
$this->base_url_choice = array();
$this->base_url_choice[] = new \stdClass;
$this->base_url_choice[0]->default_base_url = $toolProvider->baseUrl;
}
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\MediaType;
use IMSGlobal\LTI\ToolProvider;
/**
* Class to represent an LTI Tool Proxy media type
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license GNU Lesser General Public License, version 3 (<http://www.gnu.org/licenses/lgpl.html>)
*/
class ToolProxy
{
/**
* Class constructor.
*
* @param ToolProvider $toolProvider Tool Provider object
* @param ServiceDefinition $toolProxyService Tool Proxy service
* @param string $secret Shared secret
*/
function __construct($toolProvider, $toolProxyService, $secret)
{
$contexts = array();
$this->{'@context'} = array_merge(array('http://purl.imsglobal.org/ctx/lti/v2/ToolProxy'), $contexts);
$this->{'@type'} = 'ToolProxy';
$this->{'@id'} = "{$toolProxyService->endpoint}";
$this->lti_version = 'LTI-2p0';
$this->tool_consumer_profile = $toolProvider->consumer->profile->{'@id'};
$this->tool_profile = new ToolProfile($toolProvider);
$this->security_contract = new SecurityContract($toolProvider, $secret);
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\OAuth;
/**
* Class to represent an OAuth datastore
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class OAuthDataStore extends OAuth\OAuthDataStore
{
/**
* Tool Provider object.
*
* @var ToolProvider $toolProvider
*/
private $toolProvider = null;
/**
* Class constructor.
*
* @param ToolProvider $toolProvider Tool_Provider object
*/
public function __construct($toolProvider)
{
$this->toolProvider = $toolProvider;
}
/**
* Create an OAuthConsumer object for the tool consumer.
*
* @param string $consumerKey Consumer key value
*
* @return OAuthConsumer OAuthConsumer object
*/
function lookup_consumer($consumerKey)
{
return new OAuth\OAuthConsumer($this->toolProvider->consumer->getKey(),
$this->toolProvider->consumer->secret);
}
/**
* Create an OAuthToken object for the tool consumer.
*
* @param string $consumer OAuthConsumer object
* @param string $tokenType Token type
* @param string $token Token value
*
* @return OAuthToken OAuthToken object
*/
function lookup_token($consumer, $tokenType, $token)
{
return new OAuth\OAuthToken($consumer, '');
}
/**
* Lookup nonce value for the tool consumer.
*
* @param OAuthConsumer $consumer OAuthConsumer object
* @param string $token Token value
* @param string $value Nonce value
* @param string $timestamp Date/time of request
*
* @return boolean True if the nonce value already exists
*/
function lookup_nonce($consumer, $token, $value, $timestamp)
{
$nonce = new ConsumerNonce($this->toolProvider->consumer, $value);
$ok = !$nonce->load();
if ($ok) {
$ok = $nonce->save();
}
if (!$ok) {
$this->toolProvider->reason = 'Invalid nonce.';
}
return !$ok;
}
/**
* Get new request token.
*
* @param OAuthConsumer $consumer OAuthConsumer object
* @param string $callback Callback URL
*
* @return string Null value
*/
function new_request_token($consumer, $callback = null)
{
return null;
}
/**
* Get new access token.
*
* @param string $token Token value
* @param OAuthConsumer $consumer OAuthConsumer object
* @param string $verifier Verification code
*
* @return string Null value
*/
function new_access_token($token, $consumer, $verifier = null)
{
return null;
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent an outcome
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class Outcome
{
/**
* Language value.
*
* @var string $language
*/
public $language = null;
/**
* Outcome status value.
*
* @var string $status
*/
public $status = null;
/**
* Outcome date value.
*
* @var datetime $date
*/
public $date = null;
/**
* Outcome type value.
*
* @var string $type
*/
public $type = null;
/**
* Outcome data source value.
*
* @var string $dataSource
*/
public $dataSource = null;
/**
* Outcome value.
*
* @var string $value
*/
private $value = null;
/**
* Class constructor.
*
* @param string $value Outcome value (optional, default is none)
*/
public function __construct($value = null)
{
$this->value = $value;
$this->language = 'en-US';
$this->date = gmdate('Y-m-d\TH:i:s\Z', time());
$this->type = 'decimal';
}
/**
* Get the outcome value.
*
* @return string Outcome value
*/
public function getValue()
{
return $this->value;
}
/**
* Set the outcome value.
*
* @param string $value Outcome value
*/
public function setValue($value)
{
$this->value = $value;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent a tool consumer resource link share
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ResourceLinkShare
{
/**
* Consumer key value.
*
* @var string $consumerKey
*/
public $consumerKey = null;
/**
* Resource link ID value.
*
* @var string $resourceLinkId
*/
public $resourceLinkId = null;
/**
* Title of sharing context.
*
* @var string $title
*/
public $title = null;
/**
* Whether sharing request is to be automatically approved on first use.
*
* @var boolean $approved
*/
public $approved = null;
/**
* Class constructor.
*/
public function __construct()
{
}
}

View File

@ -0,0 +1,194 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent a tool consumer resource link share key
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ResourceLinkShareKey
{
/**
* Maximum permitted life for a share key value.
*/
const MAX_SHARE_KEY_LIFE = 168; // in hours (1 week)
/**
* Default life for a share key value.
*/
const DEFAULT_SHARE_KEY_LIFE = 24; // in hours
/**
* Minimum length for a share key value.
*/
const MIN_SHARE_KEY_LENGTH = 5;
/**
* Maximum length for a share key value.
*/
const MAX_SHARE_KEY_LENGTH = 32;
/**
* ID for resource link being shared.
*
* @var string $resourceLinkId
*/
public $resourceLinkId = null;
/**
* Length of share key.
*
* @var int $length
*/
public $length = null;
/**
* Life of share key.
*
* @var int $life
*/
public $life = null; // in hours
/**
* Whether the sharing arrangement should be automatically approved when first used.
*
* @var boolean $autoApprove
*/
public $autoApprove = false;
/**
* Date/time when the share key expires.
*
* @var datetime $expires
*/
public $expires = null;
/**
* Share key value.
*
* @var string $id
*/
private $id = null;
/**
* Data connector.
*
* @var DataConnector $dataConnector
*/
private $dataConnector = null;
/**
* Class constructor.
*
* @param ResourceLink $resourceLink Resource_Link object
* @param string $id Value of share key (optional, default is null)
*/
public function __construct($resourceLink, $id = null)
{
$this->initialize();
$this->dataConnector = $resourceLink->getDataConnector();
$this->resourceLinkId = $resourceLink->getRecordId();
$this->id = $id;
if (!empty($id)) {
$this->load();
}
}
/**
* Initialise the resource link share key.
*/
public function initialize()
{
$this->length = null;
$this->life = null;
$this->autoApprove = false;
$this->expires = null;
}
/**
* Initialise the resource link share key.
*
* Pseudonym for initialize().
*/
public function initialise()
{
$this->initialize();
}
/**
* Save the resource link share key to the database.
*
* @return boolean True if the share key was successfully saved
*/
public function save()
{
if (empty($this->life)) {
$this->life = self::DEFAULT_SHARE_KEY_LIFE;
} else {
$this->life = max(min($this->life, self::MAX_SHARE_KEY_LIFE), 0);
}
$this->expires = time() + ($this->life * 60 * 60);
if (empty($this->id)) {
if (empty($this->length) || !is_numeric($this->length)) {
$this->length = self::MAX_SHARE_KEY_LENGTH;
} else {
$this->length = max(min($this->length, self::MAX_SHARE_KEY_LENGTH), self::MIN_SHARE_KEY_LENGTH);
}
$this->id = DataConnector::getRandomString($this->length);
}
return $this->dataConnector->saveResourceLinkShareKey($this);
}
/**
* Delete the resource link share key from the database.
*
* @return boolean True if the share key was successfully deleted
*/
public function delete()
{
return $this->dataConnector->deleteResourceLinkShareKey($this);
}
/**
* Get share key value.
*
* @return string Share key value
*/
public function getId()
{
return $this->id;
}
###
### PRIVATE METHOD
###
/**
* Load the resource link share key from the database.
*/
private function load()
{
$this->initialize();
$this->dataConnector->loadResourceLinkShareKey($this);
if (!is_null($this->id)) {
$this->length = strlen($this->id);
}
if (!is_null($this->expires)) {
$this->life = ($this->expires - time()) / 60 / 60;
}
}
}

View File

@ -0,0 +1,128 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\Service;
use IMSGlobal\LTI\ToolProvider;
/**
* Class to implement the Membership service
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class Membership extends Service
{
/**
* The object to which the settings apply (ResourceLink, Context or ToolConsumer).
*
* @var object $source
*/
private $source;
/**
* Class constructor.
*
* @param object $source The object to which the memberships apply (ResourceLink or Context)
* @param string $endpoint Service endpoint
*/
public function __construct($source, $endpoint)
{
$consumer = $source->getConsumer();
parent::__construct($consumer, $endpoint, 'application/vnd.ims.lis.v2.membershipcontainer+json');
$this->source = $source;
}
/**
* Get the memberships.
*
* @param string $role Role for which memberships are to be requested (optional, default is all roles)
* @param int $limit Limit on the number of memberships to be returned (optional, default is all)
*
* @return mixed The array of User objects if successful, otherwise false
*/
public function get($role = null, $limit = 0) {
$isLink = is_a($this->source, 'IMSGlobal\LTI\ToolProvider\ResourceLink');
$parameters = array();
if (!empty($role)) {
$parameters['role'] = $role;
}
if ($limit > 0) {
$parameters['limit'] = strval($limit);
}
if ($isLink) {
$parameters['rlid'] = $this->source->getId();
}
$http = $this->send('GET', $parameters);
if (!$http->ok) {
$users = false;
} else {
$users = array();
if ($isLink) {
$oldUsers = $this->source->getUserResultSourcedIDs(true, ToolProvider\ToolProvider::ID_SCOPE_RESOURCE);
}
foreach ($http->responseJson->pageOf->membershipSubject->membership as $membership) {
$member = $membership->member;
if ($isLink) {
$user = ToolProvider\User::fromResourceLink($this->source, $member->userId);
} else {
$user = new ToolProvider\User();
$user->ltiUserId = $member->userId;
}
// Set the user name
$firstname = (isset($member->givenName)) ? $member->givenName : '';
$lastname = (isset($member->familyName)) ? $member->familyName : '';
$fullname = (isset($member->name)) ? $member->name : '';
$user->setNames($firstname, $lastname, $fullname);
// Set the user email
$email = (isset($member->email)) ? $member->email : '';
$user->setEmail($email, $this->source->getConsumer()->defaultEmail);
// Set the user roles
if (isset($membership->role)) {
$user->roles = ToolProvider\ToolProvider::parseRoles($membership->role);
}
// If a result sourcedid is provided save the user
if ($isLink) {
if (isset($member->message)) {
foreach ($member->message as $message) {
if (isset($message->message_type) && ($message->message_type === 'basic-lti-launch-request')) {
if (isset($message->lis_result_sourcedid)) {
$user->ltiResultSourcedId = $message->lis_result_sourcedid;
$user->save();
}
break;
}
}
}
}
$users[] = $user;
// Remove old user (if it exists)
if ($isLink) {
unset($oldUsers[$user->getId(ToolProvider\ToolProvider::ID_SCOPE_RESOURCE)]);
}
}
// Delete any old users which were not in the latest list from the tool consumer
if ($isLink) {
foreach ($oldUsers as $id => $user) {
$user->delete();
}
}
}
return $users;
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\Service;
use IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\HTTPMessage;
/**
* Class to implement a service
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class Service
{
/**
* Whether service request should be sent unsigned.
*
* @var boolean $unsigned
*/
public $unsigned = false;
/**
* Service endpoint.
*
* @var string $endpoint
*/
protected $endpoint;
/**
* Tool Consumer for this service request.
*
* @var ToolConsumer $consumer
*/
private $consumer;
/**
* Media type of message body.
*
* @var string $mediaType
*/
private $mediaType;
/**
* Class constructor.
*
* @param ToolConsumer $consumer Tool consumer object for this service request
* @param string $endpoint Service endpoint
* @param string $mediaType Media type of message body
*/
public function __construct($consumer, $endpoint, $mediaType)
{
$this->consumer = $consumer;
$this->endpoint = $endpoint;
$this->mediaType = $mediaType;
}
/**
* Send a service request.
*
* @param string $method The action type constant (optional, default is GET)
* @param array $parameters Query parameters to add to endpoint (optional, default is none)
* @param string $body Body of request (optional, default is null)
*
* @return HTTPMessage HTTP object containing request and response details
*/
public function send($method, $parameters = array(), $body = null)
{
$url = $this->endpoint;
if (!empty($parameters)) {
if (strpos($url, '?') === false) {
$sep = '?';
} else {
$sep = '&';
}
foreach ($parameters as $name => $value) {
$url .= $sep . urlencode($name) . '=' . urlencode($value);
$sep = '&';
}
}
if (!$this->unsigned) {
$header = ToolProvider\ToolConsumer::addSignature($url, $this->consumer->getKey(), $this->consumer->secret, $body, $method, $this->mediaType);
} else {
$header = null;
}
// Connect to tool consumer
$http = new HTTPMessage($url, $method, $body, $header);
// Parse JSON response
if ($http->send() && !empty($http->response)) {
$http->responseJson = json_decode($http->response);
$http->ok = !is_null($http->responseJson);
}
return $http;
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\Service;
/**
* Class to implement the Tool Settings service
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ToolSettings extends Service
{
/**
* Settings at current level mode.
*/
const MODE_CURRENT_LEVEL = 1;
/**
* Settings at all levels mode.
*/
const MODE_ALL_LEVELS = 2;
/**
* Settings with distinct names at all levels mode.
*/
const MODE_DISTINCT_NAMES = 3;
/**
* Names of LTI parameters to be retained in the consumer settings property.
*
* @var array $LEVEL_NAMES
*/
private static $LEVEL_NAMES = array('ToolProxy' => 'system',
'ToolProxyBinding' => 'context',
'LtiLink' => 'link');
/**
* The object to which the settings apply (ResourceLink, Context or ToolConsumer).
*
* @var object $source
*/
private $source;
/**
* Whether to use the simple JSON format.
*
* @var boolean $simple
*/
private $simple;
/**
* Class constructor.
*
* @param object $source The object to which the settings apply (ResourceLink, Context or ToolConsumer)
* @param string $endpoint Service endpoint
* @param boolean $simple True if the simple media type is to be used (optional, default is true)
*/
public function __construct($source, $endpoint, $simple = true)
{
if (is_a($source, 'IMSGlobal\LTI\ToolProvider\ToolConsumer')) {
$consumer = $source;
} else {
$consumer = $source->getConsumer();
}
if ($simple) {
$mediaType = 'application/vnd.ims.lti.v2.toolsettings.simple+json';
} else {
$mediaType = 'application/vnd.ims.lti.v2.toolsettings+json';
}
parent::__construct($consumer, $endpoint, $mediaType);
$this->source = $source;
$this->simple = $simple;
}
/**
* Get the tool settings.
*
* @param int $mode Mode for request (optional, default is current level only)
*
* @return mixed The array of settings if successful, otherwise false
*/
public function get($mode = self::MODE_CURRENT_LEVEL) {
$parameter = array();
if ($mode === self::MODE_ALL_LEVELS) {
$parameter['bubble'] = 'all';
} else if ($mode === self::MODE_DISTINCT_NAMES) {
$parameter['bubble'] = 'distinct';
}
$http = $this->send('GET', $parameter);
if (!$http->ok) {
$response = false;
} else if ($this->simple) {
$response = json_decode($http->response, true);
} else if (isset($http->responseJson->{'@graph'})) {
$response = array();
foreach ($http->responseJson->{'@graph'} as $level) {
$settings = json_decode(json_encode($level->custom), true);
unset($settings['@id']);
$response[self::$LEVEL_NAMES[$level->{'@type'}]] = $settings;
}
}
return $response;
}
/**
* Set the tool settings.
*
* @param array $settings An associative array of settings (optional, default is null)
*
* @return HTTPMessage HTTP object containing request and response details
*/
public function set($settings) {
if (!$this->simple) {
if (is_a($this->source, 'ToolConsumer')) {
$type = 'ToolProxy';
} else if (is_a($this->source, 'ToolConsumer')) {
$type = 'ToolProxyBinding';
} else {
$type = 'LtiLink';
}
$obj = new \stdClass();
$obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v2/ToolSettings';
$obj->{'@graph'} = array();
$level = new \stdClass();
$level->{'@type'} = $type;
$level->{'@id'} = $this->endpoint;
$level->{'custom'} = $settings;
$obj->{'@graph'}[] = $level;
$body = json_encode($obj);
} else {
$body = json_encode($settings);
}
$response = parent::send('PUT', null, $body);
return $response->ok;
}
}

View File

@ -0,0 +1,646 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\DataConnector;
use IMSGlobal\LTI\ToolProvider\Service;
use IMSGlobal\LTI\HTTPMessage;
use IMSGlobal\LTI\OAuth;
/**
* Class to represent a tool consumer
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class ToolConsumer
{
/**
* Local name of tool consumer.
*
* @var string $name
*/
public $name = null;
/**
* Shared secret.
*
* @var string $secret
*/
public $secret = null;
/**
* LTI version (as reported by last tool consumer connection).
*
* @var string $ltiVersion
*/
public $ltiVersion = null;
/**
* Name of tool consumer (as reported by last tool consumer connection).
*
* @var string $consumerName
*/
public $consumerName = null;
/**
* Tool consumer version (as reported by last tool consumer connection).
*
* @var string $consumerVersion
*/
public $consumerVersion = null;
/**
* Tool consumer GUID (as reported by first tool consumer connection).
*
* @var string $consumerGuid
*/
public $consumerGuid = null;
/**
* Optional CSS path (as reported by last tool consumer connection).
*
* @var string $cssPath
*/
public $cssPath = null;
/**
* Whether the tool consumer instance is protected by matching the consumer_guid value in incoming requests.
*
* @var boolean $protected
*/
public $protected = false;
/**
* Whether the tool consumer instance is enabled to accept incoming connection requests.
*
* @var boolean $enabled
*/
public $enabled = false;
/**
* Date/time from which the the tool consumer instance is enabled to accept incoming connection requests.
*
* @var datetime $enableFrom
*/
public $enableFrom = null;
/**
* Date/time until which the tool consumer instance is enabled to accept incoming connection requests.
*
* @var datetime $enableUntil
*/
public $enableUntil = null;
/**
* Date of last connection from this tool consumer.
*
* @var datetime $lastAccess
*/
public $lastAccess = null;
/**
* Default scope to use when generating an Id value for a user.
*
* @var int $idScope
*/
public $idScope = ToolProvider::ID_SCOPE_ID_ONLY;
/**
* Default email address (or email domain) to use when no email address is provided for a user.
*
* @var string $defaultEmail
*/
public $defaultEmail = '';
/**
* Setting values (LTI parameters, custom parameters and local parameters).
*
* @var array $settings
*/
public $settings = null;
/**
* Date/time when the object was created.
*
* @var datetime $created
*/
public $created = null;
/**
* Date/time when the object was last updated.
*
* @var datetime $updated
*/
public $updated = null;
/**
* Consumer ID value.
*
* @var int $id
*/
private $id = null;
/**
* Consumer key value.
*
* @var string $key
*/
private $key = null;
/**
* Whether the settings value have changed since last saved.
*
* @var boolean $settingsChanged
*/
private $settingsChanged = false;
/**
* Data connector object or string.
*
* @var mixed $dataConnector
*/
private $dataConnector = null;
/**
* Class constructor.
*
* @param string $key Consumer key
* @param mixed $dataConnector A data connector object
* @param boolean $autoEnable true if the tool consumers is to be enabled automatically (optional, default is false)
*/
public function __construct($key = null, $dataConnector = null, $autoEnable = false)
{
$this->initialize();
if (empty($dataConnector)) {
$dataConnector = DataConnector\DataConnector::getDataConnector();
}
$this->dataConnector = $dataConnector;
if (!empty($key)) {
$this->load($key, $autoEnable);
} else {
$this->secret = DataConnector\DataConnector::getRandomString(32);
}
}
/**
* Initialise the tool consumer.
*/
public function initialize()
{
$this->id = null;
$this->key = null;
$this->name = null;
$this->secret = null;
$this->ltiVersion = null;
$this->consumerName = null;
$this->consumerVersion = null;
$this->consumerGuid = null;
$this->profile = null;
$this->toolProxy = null;
$this->settings = array();
$this->protected = false;
$this->enabled = false;
$this->enableFrom = null;
$this->enableUntil = null;
$this->lastAccess = null;
$this->idScope = ToolProvider::ID_SCOPE_ID_ONLY;
$this->defaultEmail = '';
$this->created = null;
$this->updated = null;
}
/**
* Initialise the tool consumer.
*
* Pseudonym for initialize().
*/
public function initialise()
{
$this->initialize();
}
/**
* Save the tool consumer to the database.
*
* @return boolean True if the object was successfully saved
*/
public function save()
{
$ok = $this->dataConnector->saveToolConsumer($this);
if ($ok) {
$this->settingsChanged = false;
}
return $ok;
}
/**
* Delete the tool consumer from the database.
*
* @return boolean True if the object was successfully deleted
*/
public function delete()
{
return $this->dataConnector->deleteToolConsumer($this);
}
/**
* Get the tool consumer record ID.
*
* @return int Consumer record ID value
*/
public function getRecordId()
{
return $this->id;
}
/**
* Sets the tool consumer record ID.
*
* @param int $id Consumer record ID value
*/
public function setRecordId($id)
{
$this->id = $id;
}
/**
* Get the tool consumer key.
*
* @return string Consumer key value
*/
public function getKey()
{
return $this->key;
}
/**
* Set the tool consumer key.
*
* @param string $key Consumer key value
*/
public function setKey($key)
{
$this->key = $key;
}
/**
* Get the data connector.
*
* @return mixed Data connector object or string
*/
public function getDataConnector()
{
return $this->dataConnector;
}
/**
* Is the consumer key available to accept launch requests?
*
* @return boolean True if the consumer key is enabled and within any date constraints
*/
public function getIsAvailable()
{
$ok = $this->enabled;
$now = time();
if ($ok && !is_null($this->enableFrom)) {
$ok = $this->enableFrom <= $now;
}
if ($ok && !is_null($this->enableUntil)) {
$ok = $this->enableUntil > $now;
}
return $ok;
}
/**
* Get a setting value.
*
* @param string $name Name of setting
* @param string $default Value to return if the setting does not exist (optional, default is an empty string)
*
* @return string Setting value
*/
public function getSetting($name, $default = '')
{
if (array_key_exists($name, $this->settings)) {
$value = $this->settings[$name];
} else {
$value = $default;
}
return $value;
}
/**
* Set a setting value.
*
* @param string $name Name of setting
* @param string $value Value to set, use an empty value to delete a setting (optional, default is null)
*/
public function setSetting($name, $value = null)
{
$old_value = $this->getSetting($name);
if ($value !== $old_value) {
if (!empty($value)) {
$this->settings[$name] = $value;
} else {
unset($this->settings[$name]);
}
$this->settingsChanged = true;
}
}
/**
* Get an array of all setting values.
*
* @return array Associative array of setting values
*/
public function getSettings()
{
return $this->settings;
}
/**
* Set an array of all setting values.
*
* @param array $settings Associative array of setting values
*/
public function setSettings($settings)
{
$this->settings = $settings;
}
/**
* Save setting values.
*
* @return boolean True if the settings were successfully saved
*/
public function saveSettings()
{
if ($this->settingsChanged) {
$ok = $this->save();
} else {
$ok = true;
}
return $ok;
}
/**
* Check if the Tool Settings service is supported.
*
* @return boolean True if this tool consumer supports the Tool Settings service
*/
public function hasToolSettingsService()
{
$url = $this->getSetting('custom_system_setting_url');
return !empty($url);
}
/**
* Get Tool Settings.
*
* @param boolean $simple True if all the simple media type is to be used (optional, default is true)
*
* @return mixed The array of settings if successful, otherwise false
*/
public function getToolSettings($simple = true)
{
$url = $this->getSetting('custom_system_setting_url');
$service = new Service\ToolSettings($this, $url, $simple);
$response = $service->get();
return $response;
}
/**
* Perform a Tool Settings service request.
*
* @param array $settings An associative array of settings (optional, default is none)
*
* @return boolean True if action was successful, otherwise false
*/
public function setToolSettings($settings = array())
{
$url = $this->getSetting('custom_system_setting_url');
$service = new Service\ToolSettings($this, $url);
$response = $service->set($settings);
return $response;
}
/**
* Add the OAuth signature to an LTI message.
*
* @param string $url URL for message request
* @param string $type LTI message type
* @param string $version LTI version
* @param array $params Message parameters
*
* @return array Array of signed message parameters
*/
public function signParameters($url, $type, $version, $params)
{
if (!empty($url)) {
// Check for query parameters which need to be included in the signature
$queryParams = array();
$queryString = parse_url($url, PHP_URL_QUERY);
if (!is_null($queryString)) {
$queryItems = explode('&', $queryString);
foreach ($queryItems as $item) {
if (strpos($item, '=') !== false) {
list($name, $value) = explode('=', $item);
$queryParams[urldecode($name)] = urldecode($value);
} else {
$queryParams[urldecode($item)] = '';
}
}
}
$params = $params + $queryParams;
// Add standard parameters
$params['lti_version'] = $version;
$params['lti_message_type'] = $type;
$params['oauth_callback'] = 'about:blank';
// Add OAuth signature
$hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1();
$consumer = new OAuth\OAuthConsumer($this->getKey(), $this->secret, null);
$req = OAuth\OAuthRequest::from_consumer_and_token($consumer, null, 'POST', $url, $params);
$req->sign_request($hmacMethod, $consumer, null);
$params = $req->get_parameters();
// Remove parameters being passed on the query string
foreach (array_keys($queryParams) as $name) {
unset($params[$name]);
}
}
return $params;
}
/**
* Add the OAuth signature to an array of message parameters or to a header string.
*
* @return mixed Array of signed message parameters or header string
*/
public static function addSignature($endpoint, $consumerKey, $consumerSecret, $data, $method = 'POST', $type = null)
{
$params = array();
if (is_array($data)) {
$params = $data;
}
// Check for query parameters which need to be included in the signature
$queryParams = array();
$queryString = parse_url($endpoint, PHP_URL_QUERY);
if (!is_null($queryString)) {
$queryItems = explode('&', $queryString);
foreach ($queryItems as $item) {
if (strpos($item, '=') !== false) {
list($name, $value) = explode('=', $item);
$queryParams[urldecode($name)] = urldecode($value);
} else {
$queryParams[urldecode($item)] = '';
}
}
$params = $params + $queryParams;
}
if (!is_array($data)) {
// Calculate body hash
$hash = base64_encode(sha1($data, true));
$params['oauth_body_hash'] = $hash;
}
// Add OAuth signature
$hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1();
$oauthConsumer = new OAuth\OAuthConsumer($consumerKey, $consumerSecret, null);
$oauthReq = OAuth\OAuthRequest::from_consumer_and_token($oauthConsumer, null, $method, $endpoint, $params);
$oauthReq->sign_request($hmacMethod, $oauthConsumer, null);
$params = $oauthReq->get_parameters();
// Remove parameters being passed on the query string
foreach (array_keys($queryParams) as $name) {
unset($params[$name]);
}
if (!is_array($data)) {
$header = $oauthReq->to_header();
if (empty($data)) {
if (!empty($type)) {
$header .= "\nAccept: {$type}";
}
} else if (isset($type)) {
$header .= "\nContent-Type: {$type}";
$header .= "\nContent-Length: " . strlen($data);
}
return $header;
} else {
return $params;
}
}
/**
* Perform a service request
*
* @param object $service Service object to be executed
* @param string $method HTTP action
* @param string $format Media type
* @param mixed $data Array of parameters or body string
*
* @return HTTPMessage HTTP object containing request and response details
*/
public function doServiceRequest($service, $method, $format, $data)
{
$header = ToolConsumer::addSignature($service->endpoint, $this->getKey(), $this->secret, $data, $method, $format);
// Connect to tool consumer
$http = new HTTPMessage($service->endpoint, $method, $data, $header);
// Parse JSON response
if ($http->send() && !empty($http->response)) {
$http->responseJson = json_decode($http->response);
$http->ok = !is_null($http->responseJson);
}
return $http;
}
/**
* Load the tool consumer from the database by its record ID.
*
* @param string $id The consumer key record ID
* @param DataConnector $dataConnector Database connection object
*
* @return object ToolConsumer The tool consumer object
*/
public static function fromRecordId($id, $dataConnector)
{
$toolConsumer = new ToolConsumer(null, $dataConnector);
$toolConsumer->initialize();
$toolConsumer->setRecordId($id);
if (!$dataConnector->loadToolConsumer($toolConsumer)) {
$toolConsumer->initialize();
}
return $toolConsumer;
}
###
### PRIVATE METHOD
###
/**
* Load the tool consumer from the database.
*
* @param string $key The consumer key value
* @param boolean $autoEnable True if the consumer should be enabled (optional, default if false)
*
* @return boolean True if the consumer was successfully loaded
*/
private function load($key, $autoEnable = false)
{
$this->key = $key;
$ok = $this->dataConnector->loadToolConsumer($this);
if (!$ok) {
$this->enabled = $autoEnable;
}
return $ok;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,194 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\MediaType;
use IMSGlobal\LTI\ToolProvider\DataConnector;
/**
* Class to represent an LTI Tool Proxy
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license GNU Lesser General Public License, version 3 (<http://www.gnu.org/licenses/lgpl.html>)
*/
class ToolProxy
{
/**
* Local id of tool consumer.
*
* @var string $id
*/
public $id = null;
/**
* Tool Consumer for this tool proxy.
*
* @var ToolConsumer $consumer
*/
private $consumer = null;
/**
* Tool Consumer ID for this tool proxy.
*
* @var int $consumerId
*/
private $consumerId = null;
/**
* Consumer ID value.
*
* @var int $id
*/
private $recordId = null;
/**
* Data connector object.
*
* @var DataConnector\DataConnector $dataConnector
*/
private $dataConnector = null;
/**
* Tool Proxy document.
*
* @var MediaType\ToolProxy $toolProxy
*/
private $toolProxy = null;
/**
* Class constructor.
*
* @param DataConnector\DataConnector $dataConnector Data connector
* @param string $id Tool Proxy ID (optional, default is null)
*/
public function __construct($dataConnector, $id = null)
{
$this->initialize();
$this->dataConnector = $dataConnector;
if (!empty($id)) {
$this->load($id);
} else {
$this->recordId = DataConnector\DataConnector::getRandomString(32);
}
}
/**
* Initialise the tool proxy.
*/
public function initialize()
{
$this->id = null;
$this->recordId = null;
$this->toolProxy = null;
$this->created = null;
$this->updated = null;
}
/**
* Initialise the tool proxy.
*
* Pseudonym for initialize().
*/
public function initialise()
{
$this->initialize();
}
/**
* Get the tool proxy record ID.
*
* @return int Tool Proxy record ID value
*/
public function getRecordId()
{
return $this->recordId;
}
/**
* Sets the tool proxy record ID.
*
* @param int $recordId Tool Proxy record ID value
*/
public function setRecordId($recordId)
{
$this->recordId = $recordId;
}
/**
* Get tool consumer.
*
* @return ToolConsumer Tool consumer object for this context.
*/
public function getConsumer()
{
if (is_null($this->consumer)) {
$this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector());
}
return $this->consumer;
}
/**
* Set tool consumer ID.
*
* @param int $consumerId Tool Consumer ID for this resource link.
*/
public function setConsumerId($consumerId)
{
$this->consumer = null;
$this->consumerId = $consumerId;
}
/**
* Get the data connector.
*
* @return DataConnector Data connector object
*/
public function getDataConnector()
{
return $this->dataConnector;
}
###
### PRIVATE METHOD
###
/**
* Load the tool proxy from the database.
*
* @param string $id The tool proxy id value
*
* @return boolean True if the tool proxy was successfully loaded
*/
private function load($id)
{
$this->initialize();
$this->id = $id;
$ok = $this->dataConnector->loadToolProxy($this);
if (!$ok) {
$this->enabled = $autoEnable;
}
return $ok;
}
}

463
src/ToolProvider/User.php Normal file
View File

@ -0,0 +1,463 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent a tool consumer user
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
*/
class User
{
/**
* User's first name.
*
* @var string $firstname
*/
public $firstname = '';
/**
* User's last name (surname or family name).
*
* @var string $lastname
*/
public $lastname = '';
/**
* User's fullname.
*
* @var string $fullname
*/
public $fullname = '';
/**
* User's email address.
*
* @var string $email
*/
public $email = '';
/**
* Roles for user.
*
* @var array $roles
*/
public $roles = array();
/**
* Groups for user.
*
* @var array $groups
*/
public $groups = array();
/**
* User's result sourcedid.
*
* @var string $ltiResultSourcedId
*/
public $ltiResultSourcedId = null;
/**
* Date/time the record was created.
*
* @var object $created
*/
public $created = null;
/**
* Date/time the record was last updated.
*
* @var object $updated
*/
public $updated = null;
/**
* Resource link object.
*
* @var ResourceLink $resourceLink
*/
private $resourceLink = null;
/**
* Resource link record ID.
*
* @var int $resourceLinkId
*/
private $resourceLinkId = null;
/**
* User record ID value.
*
* @var string $id
*/
private $id = null;
/**
* user ID as supplied in the last connection request.
*
* @var string $ltiUserId
*/
public $ltiUserId = null;
/**
* Data connector object or string.
*
* @var mixed $dataConnector
*/
private $dataConnector = null;
/**
* Class constructor.
*/
public function __construct()
{
$this->initialize();
}
/**
* Initialise the user.
*/
public function initialize()
{
$this->firstname = '';
$this->lastname = '';
$this->fullname = '';
$this->email = '';
$this->roles = array();
$this->groups = array();
$this->ltiResultSourcedId = null;
$this->created = null;
$this->updated = null;
}
/**
* Initialise the user.
*
* Pseudonym for initialize().
*/
public function initialise()
{
$this->initialize();
}
/**
* Save the user to the database.
*
* @return boolean True if the user object was successfully saved
*/
public function save()
{
if (!empty($this->ltiResultSourcedId) && !is_null($this->resourceLinkId)) {
$ok = $this->getDataConnector()->saveUser($this);
} else {
$ok = true;
}
return $ok;
}
/**
* Delete the user from the database.
*
* @return boolean True if the user object was successfully deleted
*/
public function delete()
{
$ok = $this->getDataConnector()->deleteUser($this);
return $ok;
}
/**
* Get resource link.
*
* @return LTIResourceLink Resource link object
*/
public function getResourceLink()
{
if (is_null($this->resourceLink) && !is_null($this->resourceLinkId)) {
$this->resourceLink = ResourceLink::fromRecordId($this->resourceLinkId, $this->getDataConnector());
}
return $this->resourceLink;
}
/**
* Get record ID of user.
*
* @return int Record ID of user
*/
public function getRecordId()
{
return $this->id;
}
/**
* Set record ID of user.
*
* @param int $id Record ID of user
*/
public function setRecordId($id)
{
$this->id = $id;
}
/**
* Set resource link ID of user.
*
* @param int $resourceLinkId Resource link ID of user
*/
public function setResourceLinkId($resourceLinkId)
{
$this->resourceLinkId = $resourceLinkId;
}
/**
* Get the data connector.
*
* @return mixed Data connector object or string
*/
public function getDataConnector()
{
return $this->dataConnector;
}
/**
* Get the user ID (which may be a compound of the tool consumer and resource link IDs).
*
* @param int $idScope Scope to use for user ID (optional, default is null for consumer default setting)
*
* @return string User ID value
*/
public function getId($idScope = null)
{
if (empty($idScope)) {
if (!is_null($this->resourceLink)) {
$idScope = $this->resourceLink->getConsumer()->idScope;
} else {
$idScope = ToolProvider::ID_SCOPE_ID_ONLY;
}
}
switch ($idScope) {
case ToolProvider::ID_SCOPE_GLOBAL:
$id = $this->getResourceLink()->getKey() . ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId;
break;
case ToolProvider::ID_SCOPE_CONTEXT:
$id = $this->getResourceLink()->getKey();
if ($this->resourceLink->ltiContextId) {
$id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->resourceLink->ltiContextId;
}
$id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId;
break;
case ToolProvider::ID_SCOPE_RESOURCE:
$id = $this->getResourceLink()->getKey();
if ($this->resourceLink->ltiResourceLinkId) {
$id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->resourceLink->ltiResourceLinkId;
}
$id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId;
break;
default:
$id = $this->ltiUserId;
break;
}
return $id;
}
/**
* Set the user's name.
*
* @param string $firstname User's first name.
* @param string $lastname User's last name.
* @param string $fullname User's full name.
*/
public function setNames($firstname, $lastname, $fullname)
{
$names = array(0 => '', 1 => '');
if (!empty($fullname)) {
$this->fullname = trim($fullname);
$names = preg_split("/[\s]+/", $this->fullname, 2);
}
if (!empty($firstname)) {
$this->firstname = trim($firstname);
$names[0] = $this->firstname;
} else if (!empty($names[0])) {
$this->firstname = $names[0];
} else {
$this->firstname = 'User';
}
if (!empty($lastname)) {
$this->lastname = trim($lastname);
$names[1] = $this->lastname;
} else if (!empty($names[1])) {
$this->lastname = $names[1];
} else {
$this->lastname = $this->ltiUserId;
}
if (empty($this->fullname)) {
$this->fullname = "{$this->firstname} {$this->lastname}";
}
}
/**
* Set the user's email address.
*
* @param string $email Email address value
* @param string $defaultEmail Value to use if no email is provided (optional, default is none)
*/
public function setEmail($email, $defaultEmail = null)
{
if (!empty($email)) {
$this->email = $email;
} else if (!empty($defaultEmail)) {
$this->email = $defaultEmail;
if (substr($this->email, 0, 1) === '@') {
$this->email = $this->getId() . $this->email;
}
} else {
$this->email = '';
}
}
/**
* Check if the user is an administrator (at any of the system, institution or context levels).
*
* @return boolean True if the user has a role of administrator
*/
public function isAdmin()
{
return $this->hasRole('Administrator') || $this->hasRole('urn:lti:sysrole:ims/lis/SysAdmin') ||
$this->hasRole('urn:lti:sysrole:ims/lis/Administrator') || $this->hasRole('urn:lti:instrole:ims/lis/Administrator');
}
/**
* Check if the user is staff.
*
* @return boolean True if the user has a role of instructor, contentdeveloper or teachingassistant
*/
public function isStaff()
{
return ($this->hasRole('Instructor') || $this->hasRole('ContentDeveloper') || $this->hasRole('TeachingAssistant'));
}
/**
* Check if the user is a learner.
*
* @return boolean True if the user has a role of learner
*/
public function isLearner()
{
return $this->hasRole('Learner');
}
/**
* Load the user from the database.
*
* @param int $id Record ID of user
* @param DataConnector $dataConnector Database connection object
*
* @return User User object
*/
public static function fromRecordId($id, $dataConnector)
{
$user = new User();
$user->dataConnector = $dataConnector;
$user->load($id);
return $user;
}
/**
* Class constructor from resource link.
*
* @param ResourceLink $resourceLink Resource_Link object
* @param string $ltiUserId User ID value
*/
public static function fromResourceLink($resourceLink, $ltiUserId)
{
$user = new User();
$user->resourceLink = $resourceLink;
if (!is_null($resourceLink)) {
$user->resourceLinkId = $resourceLink->getRecordId();
$user->dataConnector = $resourceLink->getDataConnector();
}
$user->ltiUserId = $ltiUserId;
if (!empty($ltiUserId)) {
$user->load();
}
return $user;
}
###
### PRIVATE METHODS
###
/**
* Check whether the user has a specified role name.
*
* @param string $role Name of role
*
* @return boolean True if the user has the specified role
*/
private function hasRole($role) {
if (substr($role, 0, 4) !== 'urn:')
{
$role = 'urn:lti:role:ims/lis/' . $role;
}
return in_array($role, $this->roles);
}
/**
* Load the user from the database.
*
* @param int $id Record ID of user (optional, default is null)
*
* @return boolean True if the user object was successfully loaded
*/
private function load($id = null)
{
$this->initialize();
$this->id = $id;
$dataConnector = $this->getDataConnector();
if (!is_null($dataConnector)) {
$dataConnector->loadUser($this);
}
}
}