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:
- 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.
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:
- LTI_Tool_Provider.php
- LTI_Data_Connector_mysql.php
- LTI_Data_Connector_mysqli.php
- LTI_Data_Connector_oci.php
- LTI_Data_Connector_pdo.php
- LTI_Data_Connector_none.php
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:
- LTI_Tool_Provider
- LTI_Tool_Consumer
- LTI_Consumer_Nonce
- LTI_Resource_Link
- LTI_Resource_Link_Share
- LTI_Resource_Link_Share_Key
- LTI_User
- LTI_Outcome
- LTI_HTTP_Message
- LTI_OAuthDataStore
- LTI_Data_Connector (abstract)
- LTI_Content_Item
- LTI_Content_Item_Image
- LTI_Content_Item_Placement
The classes are fully described in the PHPDocs documentation. The following classes are now deprecated:
- LTI_Context - replaced with LTI_Resource_Link
- LTI_Context_Share - replaced with LTI_Resource_Link_Share
- LTI_Context_Share_Key - replaced with LTI_Resource_Link_Share_Key
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.
View video 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.
View video 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:
- a data source (database connection or handle)
- a table name prefix
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).
- by default the classes assume that a MySQL database connection has been established
- by name: "MySQL", "None" (names are not case sensitive)
- by database handle
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:
- 'MyApp_' - a MySQL database connection with a table name prefix of "MyApp_"
- $db - uses the MySQLi or PDO database handle with no table name prefix
- array('MyApp_', $db) - uses the MySQLi or PDO database handle with a table name prefix of "MyApp_"
- omit the parameter to use a MySQL database connection with no table name prefix
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:
- ext_resource_link_content
- ext_resource_link_content_signature
- lis_result_sourcedid
- ext_ims_lis_basic_outcome_url
- ext_ims_lis_resultvalue_sourcedids
- ext_ims_lis_memberships_id
- ext_ims_lis_memberships_url
- ext_ims_lti_tool_setting
- ext_ims_lti_tool_setting_id
- ext_ims_lti_tool_setting_url
(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:
- Tool consumer instance has not been enabled by the tool provider.
- Invalid nonce.
- OAuth signature check failed - perhaps an incorrect secret.
- Your sharing request has been refused because sharing is not being permitted.
- An error occurred initialising your share arrangement.
- You have requested to share a resource link but none is available.
- It is not possible to share your resource link with yourself.
- You have requested to share a resource link but none is available.
- You have requested to share a resource link but none is available.
- Your share request is waiting to be approved.
- You have not requested to share a resource link but an arrangement is currently in place.
- Unable to load resource link being shared.
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:
- launch requests will continue to be accepted even if a licence as expired;
- 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:
- onLaunch (for basic-lti-launch request messages)
- onConfigure (for ConfigureLaunchRequest request messages)
- onDashboard (for DashboardRequest request messages)
- onContentItem (for ContentItemSelectionRequest request messages)
- onError (for invalid request messages)
The callback method may be used to:
- create the user account if it does not already exist (or update it if it does);
- create the resource link area if it does not already exist (or update it if it does);
- set up a new session for the user (or otherwise log the user into the tool provider application);
- keep a record of the return URL for the tool consumer (for example, as a session variable);
- return the URL for the home page of the application so the user may be redirected to it (or false if the connection request is not to be accepted).
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:
- optionally set an error message to return to the user (if the tool consumer supports this facility);
- set the isOK property to FALSE.
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:
- launch
- configure
- dashboard
- content-item
- error
The return value of the callback functions should be one of the following:
- a boolean value representing success or failure of the request;
- a string starting with 'http://' or 'https://' representing the URL to redirect a user to;
- a string containing the text to be returned to the tool consumer (unless a return URL was specified in the incoming request in which case a user is redirected to this URL).
Services
The LTI_Resource_Link class provides support for the following services:
- Outcomes
- Memberships
- Setting
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
Version | Date | Description |
---|---|---|
2.0.00 | 30 June 2012 | First public release |
2.1.00 | 3 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.00 | 16 October 2012 |
Added option to return parameters sent in last extension request Now released under GNU Lesser General Public License, version 3 |
2.3.00 | 2 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.01 | 2 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.02 | 18 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.03 | 5 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.04 | 13 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.05 | 29 July 2014 | Added support for Oracle databases, content-item messages and launches from LTI 2 tool consumers. |
2.3.06 | 5 August 2014 | Fixed bug with handling of CLOB fields in Oracle databases. |
2.4.00 | 10 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.00 | 20 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.01 | 11 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 |
3.1.0 | 13 March 2019 | Updated release available in GitHub |
3.1.1 | 3 April 2019 |
Move HTTPMessage class to Http\HttpMessage (old class is deprecated) Fix PHP 7 issue with parsing XML Remove leftover test code |
Licence
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 with source files available from ceLTIc Project where it is also possible to report bugs and submit feature requests.