Overview

This set of PHP classes encapsulates the code required by an LTI 1 compliant tool provider to communicate with a tool consumer. It includes support for LTI 1.1 and the unofficial extensions to Basic LTI. These classes supercede the Basic LTI Tool Provider classes. A similar set of Java classes is also available. The Rating tool provides a sample application of this class libarary in use.

Whilst supporting LTI 1 is relatively simple, the benefits to using a class library like this one are:

System requirements

The LTI Tool Provider class is written for PHP 5.2 (or higher). Earlier versions may also work but have not been tested.

Installation

PHP source files

The download file available from OSCELOT (see Licence section below) includes the following files:

The download file also includes the following dependent PHP file:

All the files should be located in the same directory.

Data connector classes

The classes persist data through a LTI_Data_Connector class. Subclasses of this class are provided for database connections using MySQL, MySQLi, Oracle and PDO (which includes SQLite support). These subclasses use a standard data structure. The data may alternatively be merged with an existing tool provider data schema by creating a modified subclass which retrieves and updates each object from the appropriate location. To use the classes without persisting any data pass "none" as the data connector type.

The standard connectors uses the following database tables and relationships (see lti-tables-mysql.sql file):

CREATE TABLE lti_consumer (
  consumer_key varchar(255) NOT NULL,
  name varchar(45) NOT NULL,
  secret varchar(32) NOT NULL,
  lti_version varchar(12) DEFAULT NULL,
  consumer_name varchar(255) DEFAULT NULL,
  consumer_version varchar(255) DEFAULT NULL,
  consumer_guid varchar(255) DEFAULT NULL,
  css_path varchar(255) DEFAULT NULL,
  protected tinyint(1) NOT NULL,
  enabled tinyint(1) NOT NULL,
  enable_from datetime DEFAULT NULL,
  enable_until datetime DEFAULT NULL,
  last_access date DEFAULT NULL,
  created datetime NOT NULL,
  updated datetime NOT NULL,
  PRIMARY KEY (consumer_key)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE lti_context (
  consumer_key varchar(255) NOT NULL,
  context_id varchar(255) NOT NULL,
  lti_context_id varchar(255) DEFAULT NULL,
  lti_resource_id varchar(255) DEFAULT NULL,
  title varchar(255) NOT NULL,
  settings text,
  primary_consumer_key varchar(255) DEFAULT NULL,
  primary_context_id varchar(255) DEFAULT NULL,
  share_approved tinyint(1) DEFAULT NULL,
  created datetime NOT NULL,
  updated datetime NOT NULL,
  PRIMARY KEY (consumer_key, context_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE lti_user (
  consumer_key varchar(255) NOT NULL,
  context_id varchar(255) NOT NULL,
  user_id varchar(255) NOT NULL,
  lti_result_sourcedid varchar(255) NOT NULL,
  created datetime NOT NULL,
  updated datetime NOT NULL,
  PRIMARY KEY (consumer_key, context_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE lti_nonce (
  consumer_key varchar(255) NOT NULL,
  value varchar(32) NOT NULL,
  expires datetime NOT NULL,
  PRIMARY KEY (consumer_key, value)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE lti_share_key (
  share_key_id varchar(32) NOT NULL,
  primary_consumer_key varchar(255) NOT NULL,
  primary_context_id varchar(255) NOT NULL,
  auto_approve tinyint(1) NOT NULL,
  expires datetime NOT NULL,
  PRIMARY KEY (share_key_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE lti_context
  ADD CONSTRAINT lti_context_consumer_FK1 FOREIGN KEY (consumer_key)
   REFERENCES lti_consumer (consumer_key);

ALTER TABLE lti_context
  ADD CONSTRAINT lti_context_context_FK1 FOREIGN KEY (primary_consumer_key, primary_context_id)
   REFERENCES lti_context (consumer_key, context_id);

ALTER TABLE lti_user
  ADD CONSTRAINT lti_user_context_FK1 FOREIGN KEY (consumer_key, context_id)
   REFERENCES lti_context (consumer_key, context_id);

ALTER TABLE lti_nonce
  ADD CONSTRAINT lti_nonce_consumer_FK1 FOREIGN KEY (consumer_key)
   REFERENCES lti_consumer (consumer_key);

ALTER TABLE lti_share_key
  ADD CONSTRAINT lti_share_key_context_FK1 FOREIGN KEY (primary_consumer_key, primary_context_id)
   REFERENCES lti_context (consumer_key, context_id);

For a SQL Server database use a data type of tinyint in place of tinyint(1) (see lti-tables-mssql.sql file). For an Oracle database, use a data type of number in place of tinyint, timestamp in place of datetime, and clob or varchar(4000) in place of text (see lti-tables-oracle.sql and lti-tables-oracle-pdo.sql files):

An optional prefix may be added to the table names. For example, if a prefix of "MyApp_" is specified the expected table names would be MyApp_lti_consumer, MyApp_lti_context etc.

Class definitions

The following classes are defined:

The classes are fully described in the PHPDocs documentation. The following classes are now deprecated:

Usage

A quick way to see how LTI can be used to integrate an external tool with a learning environment is to watch the following screencast showing how it is being used with WebPA, a peer assessment tool. Note that, whilst this uses Blackboard Learn 9 as the learning environment, the same integration is possible with any other which provides support for the unofficial extensions, this includes WebCT, Moodle, Sakai.
Flash movie View movie illustration - An instructor's view of LTI (10:46 minutes)

Having a learning application hosted outside the learning environment also introduces opportunities for users coming from different resource links to share the same working area within the external tool. This is illustrated by the following screencast which shows how students from different resurce links (which could be from different learning environments and/or different institutions) can be joined together to work on the same peer assessment exercise.
Flash movie View movie illustration - Using LTI to allow student collaboration (6:13 minutes)

Using the PHP classes

The classes should be may available for use in a PHP file as follows:

require_once('LTI_Tool_Provider.php');

Specifying a data connector

Data is persisted by the classes via a subclass of the LTI_Data_Connector class. There are two elements to specifying a data connector:

The default data source is a pre-existing MySQL connection; the default table name prefix is an empty string (i.e. no prefix). The data source may be specified by name (for "MySQL" databases) or by database handle (the object returned when a database connection is established).

Some examples are shown below.

MySQL data connector

The following code snippets are all equivalent and set up a data connector using a MySQL connection with no table name prefix.

$db_connector = LTI_Data_Connector::getDataConnector('');
mysql_connect($db_server, $db_user, $db_password);
mysql_select_db($db_schema);

$db_connector = LTI_Data_Connector::getDataConnector('');
mysql_connect($db_server, $db_user, $db_password);
mysql_select_db($db_schema);

$db_connector = LTI_Data_Connector::getDataConnector('', 'MySQL');

MySQLi data connector

A MySQLi connection with a table name prefix of "MyApp_" can be established as follows:

$db = new mysqli($db_server, $db_user, $db_password, $db_schema);

$db_connector = LTI_Data_Connector::getDataConnector('MyApp_', $db);

Oracle data connector

An Oracle connection with a table name prefix of "MyApp_" can be established as follows:

$db = oci_connect($db_user, $db_password, '{db_host}:{db_port}/{$db_service}');

$db_connector = LTI_Data_Connector::getDataConnector('MyApp_', $db, 'oci');

Note the need to explicitly state the type as being oci when creating the data connector.

PDO data connector

The PDO data connector can be used with different databases, such as SQL Server, Oracle and SQLite. These are illustrated by the examples below.

$db = new PDO("mssql:host={$db_host};dbname={$db_schema}", $db_user, $db_password);

$db_connector = LTI_Data_Connector::getDataConnector('', $db);
$db = new PDO("oci:dbname={$db_schema}", $db_user, $db_password);

$db_connector = LTI_Data_Connector::getDataConnector('', $db);
$db = new PDO('sqlite::memory:');

$db_connector = LTI_Data_Connector::getDataConnector('', $db);

No data connector

To use the classes without any data persistence use the following:

$db_connector = LTI_Data_Connector::getDataConnector('', 'none');

Initialising a tool consumer

When a launch request is received it will be validated with the shared secret associated with the consumer key. The default data structure uses the lti_consumer table to record details of tool consumers. A record may be initialised as follows:

$consumer = new LTI_Tool_Consumer('testing.edu', $db_connector);
$consumer->name = 'Testing';
$consumer->secret = 'ThisIsASecret!';
$consumer->save();

To avoid the need to create a data connector object in advance, it is possible to pass the values from which the object may be created; for example:

By default, a tool consumer instance is disabled (i.e. will not be used to accept any incoming requests). A tool consumer may be enabled when it is created or updated as follows:

$consumer = new LTI_Tool_Consumer('testing.edu', $db_connector);
if (!is_null($consumer->created)) {
  $consumer->enabled = TRUE;
  $consumer->save();
}

Validating a launch request

The primary use case for the classes is to validate an incoming launch request from a tool consumer. Once a record has been initialised for the tool consumer (see above), the verification of the authenticity of the LTI launch request is handled automatically by the LTI_Tool_Provider class; a callback is made to a method for processing the request if the request is valid (see below). For example, if the name of the callback method is "doLaunch" the following code will check the incoming request.

class My_LTI_Tool_Provider extends LTI_Tool_Provider {

  function onLaunch() {

    // Insert code here to handle incoming connections - use the user
    // and resource_link properties of the class instance
    // to access the current user and resource link.

  }

}

$tool = new My_LTI_Tool_Provider($db_connector);
$tool->execute();

The execute method checks the authenticity of the incoming request by verifying the OAuth signature (using the shared secret recorded for the tool consumer), the timestamp is within a defined limit of the current time, and the nonce value has not been previously used. Only if the request passes all these checks is the callback function called. The following parameters are automatically saved for future use in the LTI_Resource_Link object when a launch request is executed with the LTI services:

(The LTI_Resource_Link methods of getSetting, setSetting and saveSettings may also be used with user-defined setting values.)

When a launch is not valid, a message is returned to the tool consumer ("Sorry, there was an error connecting you to the application."); this message can be changed at the top of the class file. If a custom parameter of debug=true is included in the launch then a more precise reason for the failure is returned:

Content-item message and other callbacks

If your tool also supports other types of message, then the associated methods should also be overridden; for example:

class My_LTI_Tool_Provider extends LTI_Tool_Provider {

  function onLaunch() {

    // Insert code here to handle incoming launches - use the user
    // and resource_link properties of the $tool_provider parameter
    // to access the current user and resource link.

  }

  function onConfigure() {

    // Insert code here to handle incoming configuration message requests - this
    // is an extension of the IMS specification supported by the BasicLTI Building
    // Block for Blackboard Learn 9.  It can be generated by a system administrator.

  }

  function onDashboard() {

    // Insert code here to handle incoming dashboard message requests - this
    // is an extension of the IMS specification supported by the BasicLTI Building
    // Block for Blackboard Learn 9.  It is a server-to-server request with the
    // response (typically HTML or RSS XML) being used to populate a portlet
    // (Blackboard module) displayed to the user.

  }

  function onContentItem() {

    // Insert code here to handle incoming content-item requests - use the user
    // property of the $tool_provider parameter to access the current user.

  }

  function onError() {

    // Insert code here to handle errors on incoming connections - do not expect
    // the user and resource_link properties to be populated but check the reason
    // property for the cause of the error.  Return TRUE if the error was fully
    // handled by this function.

  }

}

$tool = new My_LTI_Tool_Provider($db_connector);
$tool->execute();

Protecting a consumer key

The connection between a tool consumer and a tool provider is secured using a consumer key and a shared secret. However, there are some risks to this mechanism:

  1. launch requests will continue to be accepted even if a licence as expired;
  2. if the consumer key is used to submit launch requests from more than one tool conumser, there is a risk of clashing resource link and user IDs being received from each.

The first risk can be avoided by manually removing or disabling the consumer key as soon as the licence expires. Alternatively, the dates for any licence may be recorded for the tool consumer so that the appropriate check is made automatically when a launch request is received.

The second risk can be alleviated by setting the protected property of the tool consumer. This will cause launch requests to be only accepted from tool consumers with the same tool_consumer_guid parameter. The value of this parameter is recorded from the first launch request received using the associated consumer key. Note that this facility depends upon the tool consumer sending a value for the tool_consumer_guid parameter and each tool consumer having a unique value for this parameter.

The following code illustrates how these options may be set:

// load the tool consumer record
$consumer = new LTI_Tool_Consumer('testing.edu', $db_connector);

// set an expiry date for 30 days time
$consumer->enable_until = time() + (30 * 24 * 60 * 60);

// protect use of the consumer key to a single tool consumer
$consumer->protected = TRUE;

// save the changes
$consumer->save();

Note that the default value of the enable_from property is NULL which means that access is available immediately. A NULL value for the enable_until property means that access does not automatically expire (this is also the default).

Callback methods

A callback method is defined in the LTI_Tool_Provider class for each valid message type and for handling errors. These methods should be overridden in the subclass to perform whatever processing is required. The methods defined are:

The callback method may be used to:

Even though a request may be in accordance with the LTI specification, a tool provider may still choose to reject it because, for example, not all of the required data has been passed. A request may be rejected by a callback method as follows:

For example:

  function onLaunch() {

    ...

    $this->reason = 'Incomplete data';
    $this->isOK = FALSE;

  }

Content-item resource link IDs

One of the differences in handling a content-item message request is that any LTI links your tool passes back to be created will not yet have an associated resource link ID. One solution to this is to create an internal resource link ID for the resource and add this as a custom parameter to the link with a name of content_item_id. When a launch request is received from a resource link ID which is not recognised and this custom parameter is present, a check is made for a resource link with the value of the parameter. If found, the resource link ID is updated with the resource link ID from the launch request and so the custom parameter will be ignored on any subsequent launches. In this way, the LTI link created via a content-item request will be automatically connected to the resource link created in the tool consumer. For example, here is the code used to implement this workflow in the sample Rating application:

  ...
  $item = new LTI_Content_Item('LtiLink', $placement);
  $item->setMediaType(LTI_Content_Item::LTI_LINK_MEDIA_TYPE);
  $item->setTitle($_SESSION['title']);
  $item->setText($_SESSION['text']);
  $item->icon = new LTI_Content_Item_Image(getAppUrl() . 'images/icon50.png', 50, 50);
  $item->custom = array('content_item_id' => $_SESSION['resource_id']);
  $form_params['content_items'] = LTI_Content_Item::toJson($item);
  if (!is_null($_SESSION['data'])) {
    $form_params['data'] = $_SESSION['data'];
  }
  $data_connector = LTI_Data_Connector::getDataConnector(DB_TABLENAME_PREFIX, $db);
  $consumer = new LTI_Tool_Consumer($_SESSION['consumer_key'], $data_connector);
  $form_params = $consumer->signParameters($_SESSION['return_url'], 'ContentItemSelection', $_SESSION['lti_version'], $form_params);
  $page = LTI_Tool_Provider::sendForm($_SESSION['return_url'], $form_params);
  echo $page;
  exit;
  ...

The $_SESSION['resource_id'] variable contains a GUID generated on launch; this is used as the placeholder until the first launch of this item is performed and the validation of the request will automatically replace this resource link ID with the one passed in the launch parameters.

Callback functions (deprecated)

A callback function may be associated with the launch action according to the value passed to the constructor of the LTI_Tool_Provider object. The LTI_Tool_Provider object is passed into the callback function with the resource_link and user populated with values received via the launch request.

$tool = new LTI_Tool_Provider($db_connector, array('launch' => 'doLaunch'));
$tool->execute();

function doLaunch($tool_provider) {

// Get consumer key
  $consumer_key = $tool_provider->consumer->getKey();

// Get user ID
  $user_id = $tool_provider->user->getId();

// Get resource link ID
  $context_id = $tool_provider->resource_link->getId();

  ...

}

The following names of callback functions are recognised:

The return value of the callback functions should be one of the following:

Services

The LTI_Resource_Link class provides support for the following services:

These services are unofficial extensions of Basic LTI (LTI 1.0). An Outcomes service is also included in the LTI 1.1 specification; the classes support both versions of the Outcomes service.

In order to submit a service request, the relevant LTI_Resource_Link object is required; this object is identified by the consumer key and resource link ID:

$consumer = new LTI_Tool_Consumer($consumer_key, $db);
$resource_link = new LTI_Resource_Link($consumer, $resource_link_id);

Outcomes service

A grade book for a specific user is identified by their associated result sourcedid; these values are passed when the user launches the tool and are automatically retained by the classes for future use. The result sourcedid for the current user could be saved as a session variable as part of the callback function on launch. Alternatively, the value may be obtained by loading the LTI_User record from its user ID:

$user = new LTI_User($resource_link, $user_id);

The user's grade may be retrieved from the grade book in the tool consumer using a read operation:

$outcome = new LTI_Outcome();
if ($resource_link->doOutcomesService(LTI_Resource_Link::EXT_READ, $outcome, $user)) {
  $score = $outcome->getValue();
}

The user's grade may be saved to the grade book in the tool consumer using a write operation:

$outcome = new LTI_Outcome();
$outcome->setValue($score);
$ok = $resource_link->doOutcomesService(LTI_Resource_Link::EXT_WRITE, $outcome, $user);

The score should normally be a decimal value between 0 and 1, but the doOutcomesService method makes every effort to convert the value passed to a format which is accepted by the tool consumer; for example, a percentage of 65% may be converted to a decimal 0.65. The delete operation can be used to remove a grade from the grade book.

Memberships service

The doMembershipsService() method allows a tool provider to request a list of users with access to the resource link from the tool consumer. The method returns an array of LTI_User objects.

$users = $resource_link->doMembershipsService();

Setting service

The doSettingService($action, $value = NULL) method is used to retrieve, save and delete data in the tool consumer resource link.

$setting = $resource_link->doSettingService(LTI_Resource_Link::EXT_READ);

Note that the setting value may also be obtained from the settings of the LTI_Resource_Link (use thegetSetting('ext_ims_lti_tool_setting') call). This value is automatically updated whenever a new setting is saved.

$setting = 'Please remember this ...';
$ok = $resource_link->doSettingService(LTI_Resource_Link::EXT_WRITE, $setting);
$ok = $resource_link->doSettingService(LTI_Resource_Link::EXT_DELETE);

Version history

VersionDateDescription
2.0.0030 June 2012First public release
2.1.003 July 2012 New fields added to lti_consumer table to add an option to link a consumer key to a tool consumer GUID and record the day of last access
Enabled name of a bespoke data connector to be specified in addition to a database connection object
2.2.0016 October 2012 Added option to return parameters sent in last extension request
Now released under GNU Lesser General Public License, version 3
2.3.002 January 2013 Removed autoEnable property from LTI_Tool_Provider class (including constructor parameter)
Added LTI_Tool_Provider->setParameterConstraint() method
Changed references to $_REQUEST to $_POST
Added LTI_Tool_Consumer->getIsAvailable() method
Deprecated LTI_Context (use LTI_Resource_Link instead), other references to Context deprecated in favour of Resource_Link
Settings values in lti_context table now saved as JSON (but still reads old format)
2.3.012 February 2013 Added error callback option to LTI_Tool_Provider class
Fixed typo in setParameterConstraint function
Updated to use latest release of OAuth dependent library
Added message property to LTI_Tool_Provider class to override default message returned on error
2.3.0218 April 2013 Tightened up checking of roles - now case sensitive and checks fully qualified URN
Fixed bug with not updating a resource link before redirecting to a shared resource link
2.3.035 June 2013 Altered order of checks in authenticate
Fixed bug with LTI_Resource_Link->doOutcomesService when a resource link is shared with a different tool consumer
Separated LTI_User from LTI_Outcome object
Fixed bug with returned outcome values of zero
2.3.0413 August 2013 Added support for nonce values longer than 32 characters. For example, some OAuth libraries base64 encode the 32 character value, so to avoid extending the database field size, long values are base64 decoded and/or truncated to make them fit.
2.3.0529 July 2014 Added support for Oracle databases, content-item messages and launches from LTI 2 tool consumers.
2.3.065 August 2014 Fixed bug with handling of CLOB fields in Oracle databases.
2.4.0010 April 2015 Added methods to LTI_Tool_Provider class which are called on a successful request for each message type; use of named callback methods is deprecated
Added methods for generating signed auto-submit forms for LTI messages
Added classes for Content-item objects
Added support for unofficial ConfigureLaunchRequest and DashboardRequest messages
Oracle databases now supported using the native PHP OCI driver
2.5.0020 May 2015 Added LTI_HTTP_Message class to handle the sending of HTTP requests
Added workflow for automatically assigning resource link ID on first launch of a content-item message created link
Enhanced checking of parameter values
Added mediaTypes and documentTargets properties to LTI_Tool_Provider class for ContentItemSelectionRequest messages
2.5.0111 March 2016 Fixed bug with saving User before ResourceLink in LTI_Tool_Provider->authenticate()
Fixed bug with creating a MySQL data connector when a database connector is passed to getDataConnector()
Added check in OAuth.php that query string is set before extracting the GET parameters
Added check for incorrect version being passed in lti_version parameter

Licence

GNU Lesser General Public License This work is written by Stephen Vickers and is released under a GNU Lesser General Public Licence. The LTI Tool Provider PHP classes are available for download from OSCELOT where it is also possible to report bugs and submit feature requests.

Valid XHTML 1.0 Strict