parent
7c7ffdfc13
commit
198f58a529
18
README.md
18
README.md
|
@ -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>
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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]";
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue