Merge branch 'MDL-53832-master-review-fixes' of https://github.com/junpataleta/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2016-10-20 02:25:59 +02:00
commit ed971b90eb
71 changed files with 14803 additions and 276 deletions

View File

@ -56,6 +56,7 @@ lib/spout/
lib/amd/src/chartjs-lazy.js
lib/maxmind/GeoIp2/
lib/maxmind/MaxMind/
lib/ltiprovider/
mod/assign/feedback/editpdf/fpdi/
repository/s3/S3.php
theme/boost/scss/bootstrap/

View File

@ -57,6 +57,7 @@ lib/spout/
lib/amd/src/chartjs-lazy.js
lib/maxmind/GeoIp2/
lib/maxmind/MaxMind/
lib/ltiprovider/
mod/assign/feedback/editpdf/fpdi/
repository/s3/S3.php
theme/boost/scss/bootstrap/

View File

@ -40,7 +40,7 @@ $token = optional_param('token', $token, PARAM_ALPHANUM);
// Only show the cartridge if the token parameter is correct.
// If we do not compare with a shared secret, someone could very easily
// guess an id for the enrolment.
if (!\enrol_lti\helper::verify_tool_token($toolid, $token)) {
if (!\enrol_lti\helper::verify_cartridge_token($toolid, $token)) {
throw new \moodle_exception('incorrecttoken', 'enrol_lti');
}

View File

@ -0,0 +1,948 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Extends the IMS Tool provider library data connector for moodle.
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace enrol_lti;
defined('MOODLE_INTERNAL') || die;
use IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\ConsumerNonce;
use IMSGlobal\LTI\ToolProvider\Context;
use IMSGlobal\LTI\ToolProvider\DataConnector\DataConnector;
use IMSGlobal\LTI\ToolProvider\ResourceLink;
use IMSGlobal\LTI\ToolProvider\ResourceLinkShare;
use IMSGlobal\LTI\ToolProvider\ResourceLinkShareKey;
use IMSGlobal\LTI\ToolProvider\ToolConsumer;
use IMSGlobal\LTI\ToolProvider\ToolProxy;
use IMSGlobal\LTI\ToolProvider\User;
use stdClass;
/**
* Extends the IMS Tool provider library data connector for moodle.
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_connector extends DataConnector {
/** @var string Tool consumer table name. */
protected $consumertable;
/** @var string Context table name. */
protected $contexttable;
/** @var string Consumer nonce table name. */
protected $noncetable;
/** @var string Resource link table name. */
protected $resourcelinktable;
/** @var string Resource link share key table name. */
protected $sharekeytable;
/** @var string Tool proxy table name. */
protected $toolproxytable;
/** @var string User result table name. */
protected $userresulttable;
/**
* data_connector constructor.
*/
public function __construct() {
parent::__construct(null, 'enrol_lti_');
// Set up table names.
$this->consumertable = $this->dbTableNamePrefix . DataConnector::CONSUMER_TABLE_NAME;
$this->contexttable = $this->dbTableNamePrefix . DataConnector::CONTEXT_TABLE_NAME;
$this->noncetable = $this->dbTableNamePrefix . DataConnector::NONCE_TABLE_NAME;
$this->resourcelinktable = $this->dbTableNamePrefix . DataConnector::RESOURCE_LINK_TABLE_NAME;
$this->sharekeytable = $this->dbTableNamePrefix . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME;
$this->toolproxytable = $this->dbTableNamePrefix . DataConnector::TOOL_PROXY_TABLE_NAME;
$this->userresulttable = $this->dbTableNamePrefix . DataConnector::USER_RESULT_TABLE_NAME;
}
/**
* Load tool consumer object.
*
* @param ToolConsumer $consumer ToolConsumer object
* @return boolean True if the tool consumer object was successfully loaded
*/
public function loadToolConsumer($consumer) {
global $DB;
$id = $consumer->getRecordId();
if (!empty($id)) {
$result = $DB->get_record($this->consumertable, ['id' => $id]);
} else {
$key256 = DataConnector::getConsumerKey($consumer->getKey());
$result = $DB->get_record($this->consumertable, ['consumerkey256' => $key256]);
}
if ($result) {
if (empty($key256) || empty($result->consumerkey) || ($consumer->getKey() === $result->consumerkey)) {
$this->build_tool_consumer_object($result, $consumer);
return true;
}
}
return false;
}
/**
* Save tool consumer object.
*
* @param ToolConsumer $consumer Consumer object
* @return boolean True if the tool consumer object was successfully saved
*/
public function saveToolConsumer($consumer) {
global $DB;
$key = $consumer->getKey();
$key256 = DataConnector::getConsumerKey($key);
if ($key === $key256) {
$key = null;
}
$protected = ($consumer->protected) ? 1 : 0;
$enabled = ($consumer->enabled) ? 1 : 0;
$profile = (!empty($consumer->profile)) ? json_encode($consumer->profile) : null;
$settingsvalue = serialize($consumer->getSettings());
$now = time();
$consumer->updated = $now;
$data = [
'consumerkey256' => $key256,
'consumerkey' => $key,
'name' => $consumer->name,
'secret' => $consumer->secret,
'ltiversion' => $consumer->ltiVersion,
'consumername' => $consumer->consumerName,
'consumerversion' => $consumer->consumerVersion,
'consumerguid' => $consumer->consumerGuid,
'profile' => $profile,
'toolproxy' => $consumer->toolProxy,
'settings' => $settingsvalue,
'protected' => $protected,
'enabled' => $enabled,
'enablefrom' => $consumer->enableFrom,
'enableuntil' => $consumer->enableUntil,
'lastaccess' => $consumer->lastAccess,
'updated' => $consumer->updated,
];
$id = $consumer->getRecordId();
if (empty($id)) {
$consumer->created = $now;
$data['created'] = $consumer->created;
$id = $DB->insert_record($this->consumertable, (object) $data);
if ($id) {
$consumer->setRecordId($id);
return true;
}
} else {
$data['id'] = $id;
return $DB->update_record($this->consumertable, (object) $data);
}
return false;
}
/**
* Delete tool consumer object and related records.
*
* @param ToolConsumer $consumer Consumer object
* @return boolean True if the tool consumer object was successfully deleted
*/
public function deleteToolConsumer($consumer) {
global $DB;
$consumerpk = $consumer->getRecordId();
$deletecondition = ['consumerid' => $consumerpk];
// Delete any nonce values for this consumer.
$DB->delete_records($this->noncetable, $deletecondition);
// Delete any outstanding share keys for resource links for this consumer.
$where = "resourcelinkid IN (
SELECT rl.id
FROM {{$this->resourcelinktable}} rl
WHERE rl.consumerid = :consumerid
)";
$DB->delete_records_select($this->sharekeytable, $where, $deletecondition);
// Delete any outstanding share keys for resource links for contexts in this consumer.
$where = "resourcelinkid IN (
SELECT rl.id
FROM {{$this->resourcelinktable}} rl
INNER JOIN {{$this->contexttable}} c
ON rl.contextid = c.id
WHERE c.consumerid = :consumerid
)";
$DB->delete_records_select($this->sharekeytable, $where, $deletecondition);
// Delete any users in resource links for this consumer.
$where = "resourcelinkid IN (
SELECT rl.id
FROM {{$this->resourcelinktable}} rl
WHERE rl.consumerid = :consumerid
)";
$DB->delete_records_select($this->userresulttable, $where, $deletecondition);
// Delete any users in resource links for contexts in this consumer.
$where = "resourcelinkid IN (
SELECT rl.id
FROM {{$this->resourcelinktable}} rl
INNER JOIN {{$this->contexttable}} c
ON rl.contextid = c.id
WHERE c.consumerid = :consumerid
)";
$DB->delete_records_select($this->userresulttable, $where, $deletecondition);
// Update any resource links for which this consumer is acting as a primary resource link.
$where = "primaryresourcelinkid IN (
SELECT rl.id
FROM {{$this->resourcelinktable}} rl
WHERE rl.consumerid = :consumerid
)";
$updaterecords = $DB->get_records_select($this->resourcelinktable, $where, $deletecondition);
foreach ($updaterecords as $record) {
$record->primaryresourcelinkid = null;
$record->shareapproved = null;
$DB->update_record($this->resourcelinktable, $record);
}
// Update any resource links for contexts in which this consumer is acting as a primary resource link.
$where = "primaryresourcelinkid IN (
SELECT rl.id
FROM {{$this->resourcelinktable}} rl
INNER JOIN {{$this->contexttable}} c
ON rl.contextid = c.id
WHERE c.consumerid = :consumerid
)";
$updaterecords = $DB->get_records_select($this->resourcelinktable, $where, $deletecondition);
foreach ($updaterecords as $record) {
$record->primaryresourcelinkid = null;
$record->shareapproved = null;
$DB->update_record($this->resourcelinktable, $record);
}
// Delete any resource links for contexts in this consumer.
$where = "contextid IN (
SELECT c.id
FROM {{$this->contexttable}} c
WHERE c.consumerid = :consumerid
)";
$DB->delete_records_select($this->resourcelinktable, $where, $deletecondition);
// Delete any resource links for this consumer.
$DB->delete_records($this->resourcelinktable, $deletecondition);
// Delete any contexts for this consumer.
$DB->delete_records($this->contexttable, $deletecondition);
// Delete consumer.
$DB->delete_records($this->consumertable, ['id' => $consumerpk]);
$consumer->initialize();
return true;
}
/**
* Load all tool consumers from the database.
* @return array
*/
public function getToolConsumers() {
global $DB;
$consumers = [];
$rsconsumers = $DB->get_recordset($this->consumertable, null, 'name');
foreach ($rsconsumers as $row) {
$consumer = new ToolProvider\ToolConsumer($row->consumerkey, $this);
$this->build_tool_consumer_object($row, $consumer);
$consumers[] = $consumer;
}
$rsconsumers->close();
return $consumers;
}
/*
* ToolProxy methods.
*/
/**
* Load the tool proxy from the database.
*
* @param ToolProxy $toolproxy
* @return bool
*/
public function loadToolProxy($toolproxy) {
return false;
}
/**
* Save the tool proxy to the database.
*
* @param ToolProxy $toolproxy
* @return bool
*/
public function saveToolProxy($toolproxy) {
return false;
}
/**
* Delete the tool proxy from the database.
*
* @param ToolProxy $toolproxy
* @return bool
*/
public function deleteToolProxy($toolproxy) {
return false;
}
/*
* Context methods.
*/
/**
* Load context object.
*
* @param Context $context Context object
* @return boolean True if the context object was successfully loaded
*/
public function loadContext($context) {
global $DB;
if (!empty($context->getRecordId())) {
$params = ['id' => $context->getRecordId()];
} else {
$params = [
'consumerid' => $context->getConsumer()->getRecordId(),
'lticontextkey' => $context->ltiContextId
];
}
if ($row = $DB->get_record($this->contexttable, $params)) {
$context->setRecordId($row->id);
$context->setConsumerId($row->consumerid);
$context->ltiContextId = $row->lticontextkey;
$settings = unserialize($row->settings);
if (!is_array($settings)) {
$settings = array();
}
$context->setSettings($settings);
$context->created = $row->created;
$context->updated = $row->updated;
return true;
}
return false;
}
/**
* Save context object.
*
* @param Context $context Context object
* @return boolean True if the context object was successfully saved
*/
public function saveContext($context) {
global $DB;
$now = time();
$context->updated = $now;
$settingsvalue = serialize($context->getSettings());
$id = $context->getRecordId();
$consumerpk = $context->getConsumer()->getRecordId();
$isinsert = empty($id);
if ($isinsert) {
$context->created = $now;
$params = [
'consumerid' => $consumerpk,
'lticontextkey' => $context->ltiContextId,
'settings' => $settingsvalue,
'created' => $context->created,
'updated' => $context->updated,
];
$id = $DB->insert_record($this->contexttable, (object) $params);
if ($id) {
$context->setRecordId($id);
return true;
}
} else {
$data = (object) [
'id' => $id,
'contextid' => $consumerpk,
'lticontextkey' => $context->ltiContextId,
'settings' => $settingsvalue,
'updated' => $context->updated,
];
return $DB->update_record($this->contexttable, $data);
}
return false;
}
/**
* Delete context object.
*
* @param Context $context Context object
* @return boolean True if the Context object was successfully deleted
*/
public function deleteContext($context) {
global $DB;
$contextid = $context->getRecordId();
$params = ['id' => $contextid];
// Delete any outstanding share keys for resource links for this context.
$where = "resourcelinkid IN (
SELECT rl.id
FROM {{$this->resourcelinktable}} rl
WHERE rl.contextid = :id
)";
$DB->delete_records_select($this->sharekeytable, $where, $params);
// Delete any users in resource links for this context.
$DB->delete_records_select($this->userresulttable, $where, $params);
// Update any resource links for which this consumer is acting as a primary resource link.
$where = "primaryresourcelinkid IN (
SELECT rl.id
FROM {{$this->resourcelinktable}} rl
WHERE rl.contextid = :id
)";
$updaterecords = $DB->get_records_select($this->resourcelinktable, $where, $params);
foreach ($updaterecords as $record) {
$record->primaryresourcelinkid = null;
$record->shareapproved = null;
$DB->update_record($this->resourcelinktable, $record);
}
// Delete any resource links for this context.
$DB->delete_records($this->resourcelinktable, ['contextid' => $contextid]);
// Delete context.
$DB->delete_records($this->contexttable, $params);
$context->initialize();
return true;
}
/*
* ResourceLink methods
*/
/**
* Load resource link object.
*
* @param ResourceLink $resourcelink ResourceLink object
* @return boolean True if the resource link object was successfully loaded
*/
public function loadResourceLink($resourcelink) {
global $DB;
$resourceid = $resourcelink->getRecordId();
if (!empty($resourceid)) {
$params = ['id' => $resourceid];
$row = $DB->get_record($this->resourcelinktable, $params);
} else if (!empty($resourcelink->getContext())) {
$params = [
'contextid' => $resourcelink->getContext()->getRecordId(),
'ltiresourcelinkkey' => $resourcelink->getId()
];
$row = $DB->get_record($this->resourcelinktable, $params);
} else {
$sql = "SELECT r.*
FROM {{$this->resourcelinktable}} r
LEFT OUTER JOIN {{$this->contexttable}} c
ON r.contextid = c.id
WHERE (r.consumerid = ? OR c.consumerid = ?)
AND ltiresourcelinkkey = ?";
$params = [
$resourcelink->getConsumer()->getRecordId(),
$resourcelink->getConsumer()->getRecordId(),
$resourcelink->getId()
];
$row = $DB->get_record_sql($sql, $params);
}
if ($row) {
$resourcelink->setRecordId($row->id);
if (!is_null($row->contextid)) {
$resourcelink->setContextId($row->contextid);
} else {
$resourcelink->setContextId(null);
}
if (!is_null($row->consumerid)) {
$resourcelink->setConsumerId($row->consumerid);
} else {
$resourcelink->setConsumerId(null);
}
$resourcelink->ltiResourceLinkId = $row->ltiresourcelinkkey;
$settings = unserialize($row->settings);
if (!is_array($settings)) {
$settings = array();
}
$resourcelink->setSettings($settings);
if (!is_null($row->primaryresourcelinkid)) {
$resourcelink->primaryResourceLinkId = $row->primaryresourcelinkid;
} else {
$resourcelink->primaryResourceLinkId = null;
}
$resourcelink->shareApproved = (is_null($row->shareapproved)) ? null : ($row->shareapproved == 1);
$resourcelink->created = $row->created;
$resourcelink->updated = $row->updated;
return true;
}
return false;
}
/**
* 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) {
global $DB;
if (is_null($resourcelink->shareApproved)) {
$approved = null;
} else if ($resourcelink->shareApproved) {
$approved = 1;
} else {
$approved = 0;
}
if (empty($resourcelink->primaryResourceLinkId)) {
$primaryresourcelinkid = null;
} else {
$primaryresourcelinkid = $resourcelink->primaryResourceLinkId;
}
$now = time();
$resourcelink->updated = $now;
$settingsvalue = serialize($resourcelink->getSettings());
if (!empty($resourcelink->getContext())) {
$consumerid = null;
$contextid = $resourcelink->getContext()->getRecordId();
} else if (!empty($resourcelink->getContextId())) {
$consumerid = null;
$contextid = $resourcelink->getContextId();
} else {
$consumerid = $resourcelink->getConsumer()->getRecordId();
$contextid = null;
}
$id = $resourcelink->getRecordId();
$data = [
'consumerid' => $consumerid,
'contextid' => $contextid,
'ltiresourcelinkkey' => $resourcelink->getId(),
'settings' => $settingsvalue,
'primaryresourcelinkid' => $primaryresourcelinkid,
'shareapproved' => $approved,
'updated' => $resourcelink->updated,
];
$returnid = null;
if (empty($id)) {
$resourcelink->created = $now;
$data['created'] = $resourcelink->created;
$id = $DB->insert_record($this->resourcelinktable, (object) $data);
if ($id) {
$resourcelink->setRecordId($id);
return true;
}
} else {
$data['id'] = $id;
return $DB->update_record($this->resourcelinktable, (object) $data);
}
return false;
}
/**
* Delete resource link object.
*
* @param ResourceLink $resourcelink ResourceLink object
* @return boolean True if the resource link object and its related records were successfully deleted.
* Otherwise, a DML exception is thrown.
*/
public function deleteResourceLink($resourcelink) {
global $DB;
$resourcelinkid = $resourcelink->getRecordId();
// Delete any outstanding share keys for resource links for this consumer.
$DB->delete_records($this->sharekeytable, ['resourcelinkid' => $resourcelinkid]);
// Delete users.
$DB->delete_records($this->userresulttable, ['resourcelinkid' => $resourcelinkid]);
// Update any resource links for which this is the primary resource link.
$records = $DB->get_records($this->resourcelinktable, ['primaryresourcelinkid' => $resourcelinkid]);
foreach ($records as $record) {
$record->primaryresourcelinkid = null;
$DB->update_record($this->resourcelinktable, $record);
}
// Delete resource link.
$DB->delete_records($this->resourcelinktable, ['id' => $resourcelinkid]);
$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) {
global $DB;
$users = [];
$params = ['resourcelinkid' => $resourcelink->getRecordId()];
// Where clause for the subquery.
$subwhere = "(id = :resourcelinkid AND primaryresourcelinkid IS NULL)";
if (!$localonly) {
$subwhere .= " OR (primaryresourcelinkid = :resourcelinkid2 AND shareapproved = 1)";
$params['resourcelinkid2'] = $resourcelink->getRecordId();
}
// The subquery.
$subsql = "SELECT id
FROM {{$this->resourcelinktable}}
WHERE {$subwhere}";
// Our main where clause.
$where = "resourcelinkid IN ($subsql)";
// Fields to be queried.
$fields = 'id, ltiresultsourcedid, ltiuserkey, created, updated';
// Fetch records.
$rs = $DB->get_recordset_select($this->userresulttable, $where, $params, '', $fields);
foreach ($rs as $row) {
$user = User::fromResourceLink($resourcelink, $row->ltiuserkey);
$user->setRecordId($row->id);
$user->ltiResultSourcedId = $row->ltiresultsourcedid;
$user->created = $row->created;
$user->updated = $row->updated;
if (is_null($idscope)) {
$users[] = $user;
} else {
$users[$user->getId($idscope)] = $user;
}
}
$rs->close();
return $users;
}
/**
* Get array of shares defined for this resource link.
*
* @param ResourceLink $resourcelink ResourceLink object
* @return array Array of ResourceLinkShare objects
*/
public function getSharesResourceLink($resourcelink) {
global $DB;
$shares = [];
$params = ['primaryresourcelinkid' => $resourcelink->getRecordId()];
$fields = 'id, shareapproved, consumerid';
$records = $DB->get_records($this->resourcelinktable, $params, 'consumerid', $fields);
foreach ($records as $record) {
$share = new ResourceLinkShare();
$share->resourceLinkId = $record->id;
$share->approved = $record->shareapproved == 1;
$shares[] = $share;
}
return $shares;
}
/*
* ConsumerNonce methods
*/
/**
* Load nonce object.
*
* @param ConsumerNonce $nonce Nonce object
* @return boolean True if the nonce object was successfully loaded
*/
public function loadConsumerNonce($nonce) {
global $DB;
// Delete any expired nonce values.
$now = time();
$DB->delete_records_select($this->noncetable, "expires <= ?", [$now]);
// Load the nonce.
$params = [
'consumerid' => $nonce->getConsumer()->getRecordId(),
'value' => $nonce->getValue()
];
$result = $DB->get_field($this->noncetable, 'value', $params);
return !empty($result);
}
/**
* Save nonce object.
*
* @param ConsumerNonce $nonce Nonce object
* @return boolean True if the nonce object was successfully saved
*/
public function saveConsumerNonce($nonce) {
global $DB;
$data = [
'consumerid' => $nonce->getConsumer()->getRecordId(),
'value' => $nonce->getValue(),
'expires' => $nonce->expires
];
return $DB->insert_record($this->noncetable, (object) $data, false);
}
/*
* ResourceLinkShareKey methods.
*/
/**
* Load resource link share key object.
*
* @param ResourceLinkShareKey $sharekey ResourceLink share key object
* @return boolean True if the resource link share key object was successfully loaded
*/
public function loadResourceLinkShareKey($sharekey) {
global $DB;
// Clear expired share keys.
$now = time();
$where = "expires <= :expires";
$DB->delete_records_select($this->sharekeytable, $where, ['expires' => $now]);
// Load share key.
$fields = 'resourcelinkid, autoapprove, expires';
if ($sharekeyrecord = $DB->get_record($this->sharekeytable, ['sharekey' => $sharekey->getId()], $fields)) {
if ($sharekeyrecord->resourcelinkid == $sharekey->resourceLinkId) {
$sharekey->autoApprove = $sharekeyrecord->autoapprove == 1;
$sharekey->expires = $sharekeyrecord->expires;
return true;
}
}
return false;
}
/**
* 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) {
global $DB;
if ($sharekey->autoApprove) {
$approve = 1;
} else {
$approve = 0;
}
$expires = $sharekey->expires;
$params = [
'sharekey' => $sharekey->getId(),
'resourcelinkid' => $sharekey->resourceLinkId,
'autoapprove' => $approve,
'expires' => $expires
];
return $DB->insert_record($this->sharekeytable, (object) $params, false);
}
/**
* 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) {
global $DB;
$DB->delete_records($this->sharekeytable, ['sharekey' => $sharekey->getId()]);
$sharekey->initialize();
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) {
global $DB;
$userid = $user->getRecordId();
$fields = 'id, resourcelinkid, ltiuserkey, ltiresultsourcedid, created, updated';
if (!empty($userid)) {
$row = $DB->get_record($this->userresulttable, ['id' => $userid], $fields);
} else {
$resourcelinkid = $user->getResourceLink()->getRecordId();
$userid = $user->getId(ToolProvider\ToolProvider::ID_SCOPE_ID_ONLY);
$row = $DB->get_record_select(
$this->userresulttable,
"resourcelinkid = ? AND ltiuserkey = ?",
[$resourcelinkid, $userid],
$fields
);
}
if ($row) {
$user->setRecordId($row->id);
$user->setResourceLinkId($row->resourcelinkid);
$user->ltiUserId = $row->ltiuserkey;
$user->ltiResultSourcedId = $row->ltiresultsourcedid;
$user->created = $row->created;
$user->updated = $row->updated;
return true;
}
return false;
}
/**
* Save user object.
*
* @param User $user User object
* @return boolean True if the user object was successfully saved
*/
public function saveUser($user) {
global $DB;
$now = time();
$isinsert = is_null($user->created);
$user->updated = $now;
$params = [
'ltiresultsourcedid' => $user->ltiResultSourcedId,
'updated' => $user->updated
];
if ($isinsert) {
$params['resourcelinkid'] = $user->getResourceLink()->getRecordId();
$params['ltiuserkey'] = $user->getId(ToolProvider\ToolProvider::ID_SCOPE_ID_ONLY);
$user->created = $now;
$params['created'] = $user->created;
$id = $DB->insert_record($this->userresulttable, (object) $params);
if ($id) {
$user->setRecordId($id);
return true;
}
} else {
$params['id'] = $user->getRecordId();
return $DB->update_record($this->userresulttable, (object) $params);
}
return false;
}
/**
* Delete user object.
*
* @param User $user User object
* @return boolean True if the user object was successfully deleted
*/
public function deleteUser($user) {
global $DB;
$DB->delete_records($this->userresulttable, ['id' => $user->getRecordId()]);
$user->initialize();
return true;
}
/**
* Builds a ToolConsumer object from a record object from the DB.
*
* @param stdClass $record The DB record object.
* @param ToolConsumer $consumer
*/
protected function build_tool_consumer_object($record, ToolConsumer $consumer) {
$consumer->setRecordId($record->id);
$consumer->name = $record->name;
$key = empty($record->consumerkey) ? $record->consumerkey256 : $record->consumerkey;
$consumer->setKey($key);
$consumer->secret = $record->secret;
$consumer->ltiVersion = $record->ltiversion;
$consumer->consumerName = $record->consumername;
$consumer->consumerVersion = $record->consumerversion;
$consumer->consumerGuid = $record->consumerguid;
$consumer->profile = json_decode($record->profile);
$consumer->toolProxy = $record->toolproxy;
$settings = unserialize($record->settings);
if (!is_array($settings)) {
$settings = array();
}
$consumer->setSettings($settings);
$consumer->protected = $record->protected == 1;
$consumer->enabled = $record->enabled == 1;
$consumer->enableFrom = null;
if (!is_null($record->enablefrom)) {
$consumer->enableFrom = $record->enablefrom;
}
$consumer->enableUntil = null;
if (!is_null($record->enableuntil)) {
$consumer->enableUntil = $record->enableuntil;
}
$consumer->lastAccess = null;
if (!is_null($record->lastaccess)) {
$consumer->lastAccess = $record->lastaccess;
}
$consumer->created = $record->created;
$consumer->updated = $record->updated;
}
}

View File

@ -435,6 +435,18 @@ class helper {
return trim(html_to_text($description));
}
/**
* Returns the icon of the tool.
*
* @param stdClass $tool The lti tool
* @return moodle_url A url to the icon of the tool
* @since Moodle 3.2
*/
public static function get_icon($tool) {
global $OUTPUT;
return $OUTPUT->favicon();
}
/**
* Returns the url to the cartridge representing the tool.
*
@ -450,7 +462,7 @@ class helper {
$url = null;
$id = $tool->id;
$token = self::generate_tool_token($tool->id);
$token = self::generate_cartridge_token($tool->id);
if ($CFG->slasharguments) {
$url = new \moodle_url('/enrol/lti/cartridge.php/' . $id . '/' . $token . '/cartridge.xml');
} else {
@ -464,6 +476,34 @@ class helper {
return $url;
}
/**
* Returns the url to the tool proxy registration url.
*
* If you have slash arguments enabled, this will be a nice url ending in cartridge.xml.
* If not it will be a php page with some parameters passed.
*
* @param stdClass $tool The lti tool
* @return string The url to the cartridge representing the tool
*/
public static function get_proxy_url($tool) {
global $CFG;
$url = null;
$id = $tool->id;
$token = self::generate_proxy_token($tool->id);
if ($CFG->slasharguments) {
$url = new \moodle_url('/enrol/lti/proxy.php/' . $id . '/' . $token . '/');
} else {
$url = new \moodle_url('/enrol/lti/proxy.php',
array(
'id' => $id,
'token' => $token
)
);
}
return $url;
}
/**
* Returns a unique hash for this site and this enrolment instance.
*
@ -473,22 +513,49 @@ class helper {
* @return string MD5 hash of combined site ID and enrolment instance ID.
* @since Moodle 3.2
*/
public static function generate_tool_token($toolid) {
public static function generate_cartridge_token($toolid) {
$siteidentifier = get_site_identifier();
$checkhash = md5($siteidentifier . '_enrol_lti_' . $toolid);
$checkhash = md5($siteidentifier . '_enrol_lti_cartridge_' . $toolid);
return $checkhash;
}
/**
* Verifies that the given token matches the token of the given shared tool.
* Returns a unique hash for this site and this enrolment instance.
*
* Used to verify that the link to the proxy has not just been guessed.
*
* @param int $toolid The id of the shared tool
* @return string MD5 hash of combined site ID and enrolment instance ID.
* @since Moodle 3.2
*/
public static function generate_proxy_token($toolid) {
$siteidentifier = get_site_identifier();
$checkhash = md5($siteidentifier . '_enrol_lti_proxy_' . $toolid);
return $checkhash;
}
/**
* Verifies that the given token matches the cartridge token of the given shared tool.
*
* @param int $toolid The id of the shared tool
* @param string $token hash for this site and this enrolment instance
* @return boolean True if the token matches, false if it does not
* @since Moodle 3.2
*/
public static function verify_tool_token($toolid, $token) {
return $token == self::generate_tool_token($toolid);
public static function verify_cartridge_token($toolid, $token) {
return $token == self::generate_cartridge_token($toolid);
}
/**
* Verifies that the given token matches the proxy token of the given shared tool.
*
* @param int $toolid The id of the shared tool
* @param string $token hash for this site and this enrolment instance
* @return boolean True if the token matches, false if it does not
* @since Moodle 3.2
*/
public static function verify_proxy_token($toolid, $token) {
return $token == self::generate_proxy_token($toolid);
}
/**
@ -500,7 +567,7 @@ class helper {
* @since Moodle 3.2
*/
protected static function get_cartridge_parameters($toolid) {
global $OUTPUT, $PAGE, $SITE;
global $PAGE, $SITE;
$PAGE->set_context(\context_system::instance());
// Get the tool.
@ -510,8 +577,7 @@ class helper {
$title = self::get_name($tool);
$launchurl = self::get_launch_url($toolid);
$launchurl = $launchurl->out();
$icon = $OUTPUT->favicon();
$icon = $icon->out();
$icon = self::get_icon($tool);
$securelaunchurl = null;
$secureicon = null;
$vendorurl = new \moodle_url('/');

View File

@ -69,14 +69,16 @@ class manage_table extends \table_sql {
$this->define_columns(array(
'name',
'url',
'cartridge_url',
'secret',
'proxy_url',
'edit'
));
$this->define_headers(array(
get_string('name'),
get_string('url'),
get_string('cartridgeurl', 'enrol_lti'),
get_string('secret', 'enrol_lti'),
get_string('proxyurl', 'enrol_lti'),
get_string('edit')
));
$this->collapsible(false);
@ -102,17 +104,29 @@ class manage_table extends \table_sql {
}
/**
* Generate the URL column.
* Generate the cartridge URL column.
*
* @param \stdClass $tool event data.
* @return string
*/
public function col_url($tool) {
public function col_cartridge_url($tool) {
$url = helper::get_cartridge_url($tool);
return $this->get_copyable_text($tool, $url);
}
/**
* Generate the proxy URL column.
*
* @param \stdClass $tool event data.
* @return string
*/
public function col_proxy_url($tool) {
$url = helper::get_proxy_url($tool);
return $this->get_copyable_text($tool, $url);
}
/**
* Generate the secret column.
*

View File

@ -0,0 +1,66 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tool registration page class.
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace enrol_lti\output;
defined('MOODLE_INTERNAL') || die;
use renderable;
use renderer_base;
use templatable;
use stdClass;
/**
* Tool registration page class.
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class registration implements renderable, templatable {
/** @var returnurl The url to which the tool proxy should return */
protected $returnurl;
/**
* Construct a new tool registration page
* @param string|null $returnurl The url the consumer wants us to return the user to (optional)
*/
public function __construct($returnurl = null) {
$this->returnurl = $returnurl;
}
/**
* Export the data.
*
* @param renderer_base $output
* @return stdClass Data to be used for the template
*/
public function export_for_template(renderer_base $output) {
$data = new stdClass();
$data->returnurl = $this->returnurl;
return $data;
}
}

View File

@ -0,0 +1,50 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Renderer class for LTI enrolment
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace enrol_lti\output;
defined('MOODLE_INTERNAL') || die();
use plugin_renderer_base;
use renderable;
/**
* Renderer class for LTI enrolment
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Render the enrol_lti/proxy_registration template
*
* @param registration $registration The registration renderable
* @return string html for the page
*/
public function render_registration(registration $registration) {
$data = $registration->export_for_template($this);
return parent::render_from_template("enrol_lti/proxy_registration", $data);
}
}

View File

@ -0,0 +1,446 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Extends the IMS Tool provider library for the LTI enrolment.
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace enrol_lti;
defined('MOODLE_INTERNAL') || die;
use context;
use core\notification;
use core_user;
use enrol_lti\output\registration;
use html_writer;
use IMSGlobal\LTI\Profile\Item;
use IMSGlobal\LTI\Profile\Message;
use IMSGlobal\LTI\Profile\ResourceHandler;
use IMSGlobal\LTI\Profile\ServiceDefinition;
use IMSGlobal\LTI\ToolProvider\ToolProvider;
use moodle_exception;
use moodle_url;
use stdClass;
require_once($CFG->dirroot . '/user/lib.php');
/**
* Extends the IMS Tool provider library for the LTI enrolment.
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_provider extends ToolProvider {
/**
* @var stdClass $tool The object representing the enrol instance providing this LTI tool
*/
protected $tool;
/**
* Remove $this->baseUrl (wwwroot) from a given url string and return it.
*
* @param string $url The url from which to remove the base url
* @return string|null A string of the relative path to the url, or null if it couldn't be determined.
*/
protected function strip_base_url($url) {
if (substr($url, 0, strlen($this->baseUrl)) == $this->baseUrl) {
return substr($url, strlen($this->baseUrl));
}
return null;
}
/**
* Create a new instance of tool_provider to handle all the LTI tool provider interactions.
*
* @param int $toolid The id of the tool to be provided.
*/
public function __construct($toolid) {
global $CFG, $SITE;
$token = helper::generate_proxy_token($toolid);
$tool = helper::get_lti_tool($toolid);
$this->tool = $tool;
$dataconnector = new data_connector();
parent::__construct($dataconnector);
// Override debugMode and set to the configured value.
$this->debugMode = $CFG->debugdeveloper;
$this->baseUrl = $CFG->wwwroot;
$toolpath = helper::get_launch_url($toolid);
$toolpath = $this->strip_base_url($toolpath);
$vendorid = $SITE->shortname;
$vendorname = $SITE->fullname;
$vendordescription = trim(html_to_text($SITE->summary));
$this->vendor = new Item($vendorid, $vendorname, $vendordescription, $CFG->wwwroot);
$name = helper::get_name($tool);
$description = helper::get_description($tool);
$icon = helper::get_icon($tool)->out();
$icon = $this->strip_base_url($icon);
$this->product = new Item(
$token,
$name,
$description,
helper::get_proxy_url($tool),
'1.0'
);
$requiredmessages = [
new Message(
'basic-lti-launch-request',
$toolpath,
[
'Context.id',
'CourseSection.title',
'CourseSection.label',
'CourseSection.sourcedId',
'CourseSection.longDescription',
'CourseSection.timeFrame.begin',
'ResourceLink.id',
'ResourceLink.title',
'ResourceLink.description',
'User.id',
'User.username',
'Person.name.full',
'Person.name.given',
'Person.name.family',
'Person.email.primary',
'Person.sourcedId',
'Person.name.middle',
'Person.address.street1',
'Person.address.locality',
'Person.address.country',
'Person.address.timezone',
'Person.phone.primary',
'Person.phone.mobile',
'Person.webaddress',
'Membership.role',
'Result.sourcedId',
'Result.autocreate'
]
)
];
$optionalmessages = [
];
$this->resourceHandlers[] = new ResourceHandler(
new Item(
$token,
helper::get_name($tool),
$description
),
$icon,
$requiredmessages,
$optionalmessages
);
$this->requiredServices[] = new ServiceDefinition(['application/vnd.ims.lti.v2.toolproxy+json'], ['POST']);
}
/**
* Override onError for custom error handling.
* @return void
*/
protected function onError() {
global $OUTPUT;
$message = $this->message;
if ($this->debugMode && !empty($this->reason)) {
$message = $this->reason;
}
// Display the error message from the provider's side if the consumer has not specified a URL to pass the error to.
if (empty($this->returnUrl)) {
$this->errorOutput = $OUTPUT->notification(get_string('failedrequest', 'enrol_lti', ['reason' => $message]), 'error');
}
}
/**
* Override onLaunch with tool logic.
* @return void
*/
protected function onLaunch() {
global $DB, $SESSION, $CFG;
// Check for valid consumer.
if (empty($this->consumer) || $this->dataConnector->loadToolConsumer($this->consumer) === false) {
$this->ok = false;
$this->message = get_string('invalidtoolconsumer', 'enrol_lti');
return;
}
$url = helper::get_launch_url($this->tool->id);
// If a tool proxy has been stored for the current consumer trying to access a tool,
// check that the tool is being launched from the correct url.
$correctlaunchurl = false;
if (!empty($this->consumer->toolProxy)) {
$proxy = json_decode($this->consumer->toolProxy);
$handlers = $proxy->tool_profile->resource_handler;
foreach ($handlers as $handler) {
foreach ($handler->message as $message) {
$handlerurl = new moodle_url($message->path);
$fullpath = $handlerurl->out(false);
if ($message->message_type == "basic-lti-launch-request" && $fullpath == $url) {
$correctlaunchurl = true;
break 2;
}
}
}
} else if ($this->tool->secret == $this->consumer->secret) {
// Test if the LTI1 secret for this tool is being used. Then we know the correct tool is being launched.
$correctlaunchurl = true;
}
if (!$correctlaunchurl) {
$this->ok = false;
$this->message = get_string('invalidrequest', 'enrol_lti');
return;
}
// Before we do anything check that the context is valid.
$tool = $this->tool;
$context = context::instance_by_id($tool->contextid);
// Set the user data.
$user = new stdClass();
$user->username = helper::create_username($this->consumer->getKey(), $this->user->ltiUserId);
if (!empty($this->user->firstname)) {
$user->firstname = $this->user->firstname;
} else {
$user->firstname = $this->user->getRecordId();
}
if (!empty($this->user->lastname)) {
$user->lastname = $this->user->lastname;
} else {
$user->lastname = $this->tool->contextid;
}
$user->email = core_user::clean_field($this->user->email, 'email');
// Get the user data from the LTI consumer.
$user = helper::assign_user_tool_data($tool, $user);
// Check if the user exists.
if (!$dbuser = $DB->get_record('user', ['username' => $user->username, 'deleted' => 0])) {
// If the email was stripped/not set then fill it with a default one. This
// stops the user from being redirected to edit their profile page.
if (empty($user->email)) {
$user->email = $user->username . "@example.com";
}
$user->auth = 'lti';
$user->id = \user_create_user($user);
// Get the updated user record.
$user = $DB->get_record('user', ['id' => $user->id]);
} else {
if (helper::user_match($user, $dbuser)) {
$user = $dbuser;
} else {
// If email is empty remove it, so we don't update the user with an empty email.
if (empty($user->email)) {
unset($user->email);
}
$user->id = $dbuser->id;
\user_update_user($user);
// Get the updated user record.
$user = $DB->get_record('user', ['id' => $user->id]);
}
}
// Update user image.
if (isset($this->user) && isset($this->user->image) && !empty($this->user->image)) {
$image = $this->user->image;
} else {
// Use custom_user_image parameter as a fallback.
$image = $this->resourceLink->getSetting('custom_user_image');
}
// Check if there is an image to process.
if ($image) {
helper::update_user_profile_image($user->id, $image);
}
// Check if we are an instructor.
$isinstructor = $this->user->isStaff() || $this->user->isAdmin();
if ($context->contextlevel == CONTEXT_COURSE) {
$courseid = $context->instanceid;
$urltogo = new moodle_url('/course/view.php', ['id' => $courseid]);
// May still be set from previous session, so unset it.
unset($SESSION->forcepagelayout);
} else if ($context->contextlevel == CONTEXT_MODULE) {
$cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST);
$urltogo = new moodle_url('/mod/' . $cm->modname . '/view.php', ['id' => $cm->id]);
// If we are a student in the course module context we do not want to display blocks.
if (!$isinstructor) {
// Force the page layout.
$SESSION->forcepagelayout = 'embedded';
} else {
// May still be set from previous session, so unset it.
unset($SESSION->forcepagelayout);
}
} else {
print_error('invalidcontext');
exit();
}
// Enrol the user in the course with no role.
$result = helper::enrol_user($tool, $user->id);
// Display an error, if there is one.
if ($result !== helper::ENROLMENT_SUCCESSFUL) {
print_error($result, 'enrol_lti');
exit();
}
// Give the user the role in the given context.
$roleid = $isinstructor ? $tool->roleinstructor : $tool->rolelearner;
role_assign($roleid, $user->id, $tool->contextid);
// Login user.
$sourceid = $this->user->ltiResultSourcedId;
$serviceurl = $this->resourceLink->getSetting('lis_outcome_service_url');
// Check if we have recorded this user before.
if ($userlog = $DB->get_record('enrol_lti_users', ['toolid' => $tool->id, 'userid' => $user->id])) {
if ($userlog->sourceid != $sourceid) {
$userlog->sourceid = $sourceid;
}
if ($userlog->serviceurl != $serviceurl) {
$userlog->serviceurl = $serviceurl;
}
$userlog->lastaccess = time();
$DB->update_record('enrol_lti_users', $userlog);
} else {
// Add the user details so we can use it later when syncing grades and members.
$userlog = new stdClass();
$userlog->userid = $user->id;
$userlog->toolid = $tool->id;
$userlog->serviceurl = $serviceurl;
$userlog->sourceid = $sourceid;
$userlog->consumerkey = $this->consumer->getKey();
$userlog->consumersecret = $tool->secret;
$userlog->lastgrade = 0;
$userlog->lastaccess = time();
$userlog->timecreated = time();
$userlog->membershipsurl = $this->resourceLink->getSetting('ext_ims_lis_memberships_url');
$userlog->membershipsid = $this->resourceLink->getSetting('ext_ims_lis_memberships_id');
$DB->insert_record('enrol_lti_users', $userlog);
}
// Finalise the user log in.
complete_user_login($user);
// Everything's good. Set appropriate OK flag and message values.
$this->ok = true;
$this->message = get_string('success');
if (empty($CFG->allowframembedding)) {
// Provide an alternative link.
$stropentool = get_string('opentool', 'enrol_lti');
echo html_writer::tag('p', get_string('frameembeddingnotenabled', 'enrol_lti'));
echo html_writer::link($urltogo, $stropentool, ['target' => '_blank']);
} else {
// All done, redirect the user to where they want to go.
redirect($urltogo);
}
}
/**
* Override onRegister with registration code.
*/
protected function onRegister() {
global $PAGE;
if (empty($this->consumer)) {
$this->ok = false;
$this->message = get_string('invalidtoolconsumer', 'enrol_lti');
return;
}
if (empty($this->returnUrl)) {
$this->ok = false;
$this->message = get_string('returnurlnotset', 'enrol_lti');
return;
}
if ($this->doToolProxyService()) {
// Map tool consumer and published tool, if necessary.
$this->map_tool_to_consumer();
// Indicate successful processing in message.
$this->message = get_string('successfulregistration', 'enrol_lti');
// Prepare response.
$returnurl = new moodle_url($this->returnUrl);
$returnurl->param('lti_msg', get_string("successfulregistration", "enrol_lti"));
$returnurl->param('status', 'success');
$guid = $this->consumer->getKey();
$returnurl->param('tool_proxy_guid', $guid);
$returnurlout = $returnurl->out(false);
$registration = new registration($returnurlout);
$output = $PAGE->get_renderer('enrol_lti');
echo $output->render($registration);
} else {
// Tell the consumer that the registration failed.
$this->ok = false;
$this->message = get_string('couldnotestablishproxy', 'enrol_lti');
}
}
/**
* Performs mapping of the tool consumer to a published tool.
*
* @throws moodle_exception
*/
public function map_tool_to_consumer() {
global $DB;
if (empty($this->consumer)) {
throw new moodle_exception('invalidtoolconsumer', 'enrol_lti');
}
// Map the consumer to the tool.
$mappingparams = [
'toolid' => $this->tool->id,
'consumerid' => $this->consumer->getRecordId()
];
$mappingexists = $DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams);
if (!$mappingexists) {
$DB->insert_record('enrol_lti_tool_consumer_map', (object) $mappingparams);
}
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="enrol/lti/db" VERSION="20160322" COMMENT="XMLDB file for Moodle enrol/lti"
<XMLDB PATH="enrol/lti/db" VERSION="20160809" COMMENT="XMLDB file for Moodle enrol/lti"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
@ -53,5 +53,134 @@
<KEY NAME="toolid" TYPE="foreign" FIELDS="toolid" REFTABLE="enrol_lti_tools" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="enrol_lti_lti2_consumer" COMMENT="LTI consumers interacting with moodle">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="consumerkey256" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="consumerkey" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="secret" TYPE="char" LENGTH="1024" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="ltiversion" TYPE="char" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="consumername" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="consumerversion" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="consumerguid" TYPE="char" LENGTH="1024" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="profile" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="toolproxy" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="settings" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="protected" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="enablefrom" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="enableuntil" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="lastaccess" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="created" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="updated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="consumerkey256_uniq" UNIQUE="true" FIELDS="consumerkey256"/>
</INDEXES>
</TABLE>
<TABLE NAME="enrol_lti_lti2_tool_proxy" COMMENT="A tool proxy between moodle and a consumer">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="toolproxykey" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="consumerid" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="toolproxy" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="created" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="updated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="toolproxykey_uniq" TYPE="unique" FIELDS="toolproxykey"/>
<KEY NAME="consumerid" TYPE="foreign" FIELDS="consumerid" REFTABLE="enrol_lti_lti2_consumer" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="enrol_lti_lti2_context" COMMENT="Information about a specific LTI contexts from the consumers">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="consumerid" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="lticontextkey" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="settings" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="created" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="updated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="consumerid" TYPE="foreign" FIELDS="consumerid" REFTABLE="enrol_lti_lti2_consumer" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="enrol_lti_lti2_nonce" COMMENT="Nonce used for authentication between moodle and a consumer">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="consumerid" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="value" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="expires" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="consumerid" TYPE="foreign" FIELDS="consumerid" REFTABLE="enrol_lti_lti2_consumer" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="enrol_lti_lti2_resource_link" COMMENT="Link from the consumer to the tool">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="11" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="consumerid" TYPE="int" LENGTH="11" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="ltiresourcelinkkey" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="settings" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="primaryresourcelinkid" TYPE="int" LENGTH="11" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="shareapproved" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="created" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="updated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="enrol_lti_lti2_context" REFFIELDS="id"/>
<KEY NAME="primaryresourcelinkid" TYPE="foreign" FIELDS="primaryresourcelinkid" REFTABLE="enrol_lti_lti2_resource_link" REFFIELDS="id"/>
<KEY NAME="consumerid" TYPE="foreign" FIELDS="consumerid" REFTABLE="enrol_lti_lti2_consumer" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="enrol_lti_lti2_share_key" COMMENT="Resource link share key">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="sharekey" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="resourcelinkid" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="autoapprove" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="expires" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="sharekey" TYPE="unique" FIELDS="sharekey" />
<KEY NAME="resourcelinkid" TYPE="foreign-unique" FIELDS="resourcelinkid" REFTABLE="enrol_lti_lti2_resource_link" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="enrol_lti_lti2_user_result" COMMENT="Results for each user for each resource link">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="resourcelinkid" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="ltiuserkey" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="ltiresultsourcedid" TYPE="char" LENGTH="1024" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="created" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="updated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="resourcelinkid" TYPE="foreign" FIELDS="resourcelinkid" REFTABLE="enrol_lti_lti2_resource_link" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="enrol_lti_tool_consumer_map" COMMENT="Table that maps the published tool to tool consumers.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="toolid" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false" COMMENT="The tool ID."/>
<FIELD NAME="consumerid" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false" COMMENT="The consumer ID."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="toolid" TYPE="foreign" FIELDS="toolid" REFTABLE="enrol_lti_tools" REFFIELDS="id"/>
<KEY NAME="consumerid" TYPE="foreign" FIELDS="consumerid" REFTABLE="enrol_lti_lti2_consumer" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>

231
enrol/lti/db/upgrade.php Normal file
View File

@ -0,0 +1,231 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
//
/**
* This file keeps track of upgrades to the lti enrolment plugin
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* xmldb_lti_upgrade is the function that upgrades
* the lti module database when is needed
*
* This function is automaticly called when version number in
* version.php changes.
*
* @param int $oldversion New old version number.
*
* @return boolean
*/
function xmldb_enrol_lti_upgrade($oldversion) {
global $DB;
$dbman = $DB->get_manager();
if ($oldversion < 2016052303) {
// Define table enrol_lti_lti2_consumer to be created.
$table = new xmldb_table('enrol_lti_lti2_consumer');
// Adding fields to table enrol_lti_lti2_consumer.
$table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('name', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null);
$table->add_field('consumerkey256', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('consumerkey', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_field('secret', XMLDB_TYPE_CHAR, '1024', null, XMLDB_NOTNULL, null, null);
$table->add_field('ltiversion', XMLDB_TYPE_CHAR, '10', null, null, null, null);
$table->add_field('consumername', XMLDB_TYPE_CHAR, '255', null, null, null, null);
$table->add_field('consumerversion', XMLDB_TYPE_CHAR, '255', null, null, null, null);
$table->add_field('consumerguid', XMLDB_TYPE_CHAR, '1024', null, null, null, null);
$table->add_field('profile', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_field('toolproxy', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_field('settings', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_field('protected', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
$table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
$table->add_field('enablefrom', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
$table->add_field('enableuntil', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
$table->add_field('lastaccess', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
$table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table enrol_lti_lti2_consumer.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
// Adding indexes to table enrol_lti_lti2_consumer.
$table->add_index('consumerkey256_uniq', XMLDB_INDEX_UNIQUE, array('consumerkey256'));
// Conditionally launch create table for enrol_lti_lti2_consumer.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table enrol_lti_lti2_tool_proxy to be created.
$table = new xmldb_table('enrol_lti_lti2_tool_proxy');
// Adding fields to table enrol_lti_lti2_tool_proxy.
$table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('toolproxykey', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
$table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
$table->add_field('toolproxy', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
$table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table enrol_lti_lti2_tool_proxy.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('toolproxykey_uniq', XMLDB_KEY_UNIQUE, array('toolproxykey'));
$table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
// Conditionally launch create table for enrol_lti_lti2_tool_proxy.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table enrol_lti_lti2_context to be created.
$table = new xmldb_table('enrol_lti_lti2_context');
// Adding fields to table enrol_lti_lti2_context.
$table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
$table->add_field('lticontextkey', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('settings', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table enrol_lti_lti2_context.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
// Conditionally launch create table for enrol_lti_lti2_context.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table enrol_lti_lti2_nonce to be created.
$table = new xmldb_table('enrol_lti_lti2_nonce');
// Adding fields to table enrol_lti_lti2_nonce.
$table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
$table->add_field('value', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
$table->add_field('expires', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table enrol_lti_lti2_nonce.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
// Conditionally launch create table for enrol_lti_lti2_nonce.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table enrol_lti_lti2_resource_link to be created.
$table = new xmldb_table('enrol_lti_lti2_resource_link');
// Adding fields to table enrol_lti_lti2_resource_link.
$table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '11', null, null, null, null);
$table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, null, null, null);
$table->add_field('ltiresourcelinkkey', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('settings', XMLDB_TYPE_TEXT, null, null, null, null, null);
$table->add_field('primaryresourcelinkid', XMLDB_TYPE_INTEGER, '11', null, null, null, null);
$table->add_field('shareapproved', XMLDB_TYPE_INTEGER, '1', null, null, null, null);
$table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table enrol_lti_lti2_resource_link.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('contextid', XMLDB_KEY_FOREIGN, array('contextid'), 'enrol_lti_lti2_context', array('id'));
$table->add_key('primaryresourcelinkid', XMLDB_KEY_FOREIGN, array('primaryresourcelinkid'),
'enrol_lti_lti2_resource_link', array('id'));
$table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
// Conditionally launch create table for enrol_lti_lti2_resource_link.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table enrol_lti_lti2_share_key to be created.
$table = new xmldb_table('enrol_lti_lti2_share_key');
// Adding fields to table enrol_lti_lti2_share_key.
$table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('sharekey', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
$table->add_field('resourcelinkid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
$table->add_field('autoapprove', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
$table->add_field('expires', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table enrol_lti_lti2_share_key.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('sharekey', XMLDB_KEY_UNIQUE, array('sharekey'));
$table->add_key('resourcelinkid', XMLDB_KEY_FOREIGN_UNIQUE, array('resourcelinkid'),
'enrol_lti_lti2_resource_link', array('id'));
// Conditionally launch create table for enrol_lti_lti2_share_key.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table enrol_lti_lti2_user_result to be created.
$table = new xmldb_table('enrol_lti_lti2_user_result');
// Adding fields to table enrol_lti_lti2_user_result.
$table->add_field('id', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('resourcelinkid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
$table->add_field('ltiuserkey', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('ltiresultsourcedid', XMLDB_TYPE_CHAR, '1024', null, XMLDB_NOTNULL, null, null);
$table->add_field('created', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('updated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table enrol_lti_lti2_user_result.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('resourcelinkid', XMLDB_KEY_FOREIGN, array('resourcelinkid'),
'enrol_lti_lti2_resource_link', array('id'));
// Conditionally launch create table for enrol_lti_lti2_user_result.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table enrol_lti_tool_consumer_map to be created.
$table = new xmldb_table('enrol_lti_tool_consumer_map');
// Adding fields to table enrol_lti_tool_consumer_map.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('toolid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
$table->add_field('consumerid', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null);
// Adding keys to table enrol_lti_tool_consumer_map.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('toolid', XMLDB_KEY_FOREIGN, array('toolid'), 'enrol_lti_tools', array('id'));
$table->add_key('consumerid', XMLDB_KEY_FOREIGN, array('consumerid'), 'enrol_lti_lti2_consumer', array('id'));
// Conditionally launch create table for enrol_lti_tool_consumer_map.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Lti savepoint reached.
upgrade_plugin_savepoint(true, 2016052303, 'enrol', 'lti');
}
return true;
}

View File

@ -8,6 +8,7 @@ require_once($CFG->dirroot . '/enrol/lti/ims-blti/TrivialOAuthDataStore.php');
function is_basic_lti_request() {
$good_message_type = $_REQUEST["lti_message_type"] == "basic-lti-launch-request";
$good_lti_version = ($_REQUEST["lti_version"] == "LTI-1p0" or $_REQUEST["lti_version"] == "LTI-1.0");
$good_lti_version = $good_lti_version || ($_REQUEST["lti_version"] == "LTI-2p0" or $_REQUEST["lti_version"] == "LTI-2.0");
$resource_link_id = $_REQUEST["resource_link_id"];
if ($good_message_type and $good_lti_version and isset($resource_link_id) ) return(true);
return false;

View File

@ -24,6 +24,8 @@
$string['allowframeembedding'] = 'Note: It is recommended that the site administration setting \'Allow frame embedding\' is enabled, so that tools are displayed within a frame rather than in a new window.';
$string['authltimustbeenabled'] = 'Note: This plugin requires the LTI authentication plugin to be enabled too.';
$string['cartridgeurl'] = 'Cartridge URL';
$string['couldnotestablishproxy'] = 'Could not establish proxy with consumer.';
$string['enrolenddate'] = 'End date';
$string['enrolenddate_help'] = 'If enabled, users can access until this date only.';
$string['enrolenddateerror'] = 'Enrolment end date cannot be earlier than start date';
@ -35,9 +37,12 @@ $string['enrolmentnotstarted'] = 'Enrolment has not started.';
$string['enrolstartdate'] = 'Start date';
$string['enrolstartdate_help'] = 'If enabled, users can access from this date onward only.';
$string['frameembeddingnotenabled'] = 'To access the tool, please follow the link below.';
$string['failedrequest'] = 'Failed request. Reason: {$a->reason}';
$string['gradesync'] = 'Grade synchronisation';
$string['gradesync_help'] = 'Whether grades from the tool are sent to the remote system (LTI consumer).';
$string['incorrecttoken'] = 'The token was incorrect. Please check the URL and try again, or contact the administrator of this tool.';
$string['invalidrequest'] = 'Invalid request';
$string['invalidtoolconsumer'] = 'Invalid tool consumer.';
$string['maxenrolled'] = 'Maximum enrolled users';
$string['maxenrolled_help'] = 'The maximum number of remote users who can access the tool. If set to zero, the number of enrolled users is unlimited.';
$string['maxenrolledreached'] = 'The maximum number of remote users allowed to access the tool has been reached.';
@ -54,8 +59,11 @@ $string['lti:unenrol'] = 'Unenrol users from the course';
$string['opentool'] = 'Open tool';
$string['pluginname'] = 'Publish as LTI tool';
$string['pluginname_desc'] = 'The \'Publish as LTI tool\' plugin, together with the LTI authentication plugin, allows remote users to access selected courses and activities. In other words, Moodle functions as an LTI tool provider.';
$string['proxyurl'] = 'Proxy URL';
$string['registration'] = 'Published tool registration';
$string['remotesystem'] = 'Remote system';
$string['requirecompletion'] = 'Require course or activity completion prior to grade synchronisation';
$string['returnurlnotset'] = 'Return URL was not set.';
$string['roleinstructor'] = 'Role for teacher';
$string['roleinstructor_help'] = 'The role assigned in the tool to the remote teacher.';
$string['rolelearner'] = 'Role for student';
@ -63,6 +71,7 @@ $string['rolelearner_help'] = 'The role assigned in the tool to the remote stude
$string['secret'] = 'Secret';
$string['secret_help'] = 'A string of characters which is shared with the remote system (LTI consumer) to provide access to the tool.';
$string['sharedexternaltools'] = 'Published as LTI tools';
$string['successfulregistration'] = 'Successful registration';
$string['tasksyncgrades'] = 'Publish as LTI tool grade sync';
$string['tasksyncmembers'] = 'Publish as LTI tool users sync';
$string['toolsprovided'] = 'Published tools';

View File

@ -22,6 +22,9 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use enrol_lti\data_connector;
use IMSGlobal\LTI\ToolProvider\ToolConsumer;
defined('MOODLE_INTERNAL') || die();
/**
@ -157,6 +160,21 @@ class enrol_lti_plugin extends enrol_plugin {
// Delete any users associated with this tool.
$DB->delete_records('enrol_lti_users', array('toolid' => $tool->id));
// Get tool and consumer mappings.
$rsmapping = $DB->get_recordset('enrol_lti_tool_consumer_map', array('toolid' => $tool->id));
// Delete consumers that are linked to this tool and their related data.
$dataconnector = new data_connector();
foreach ($rsmapping as $mapping) {
$consumer = new ToolConsumer(null, $dataconnector);
$consumer->setRecordId($mapping->consumerid);
$dataconnector->deleteToolConsumer($consumer);
}
$rsmapping->close();
// Delete mapping records.
$DB->delete_records('enrol_lti_tool_consumer_map', array('toolid' => $tool->id));
// Delete the lti tool record.
$DB->delete_records('enrol_lti_tools', array('id' => $tool->id));

80
enrol/lti/proxy.php Normal file
View File

@ -0,0 +1,80 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tool proxy.
*
* @package enrol_lti
* @copyright 2016 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
$toolid = null;
$token = null;
$filearguments = get_file_argument();
$arguments = explode('/', trim($filearguments, '/'));
if (count($arguments) == 2) {
list($toolid, $token) = $arguments;
}
$toolid = optional_param('id', $toolid, PARAM_INT);
$token = optional_param('token', $token, PARAM_ALPHANUM);
$PAGE->set_context(context_system::instance());
$url = new moodle_url('/enrol/lti/tp.php');
$PAGE->set_url($url);
$PAGE->set_pagelayout('popup');
$PAGE->set_title(get_string('registration', 'enrol_lti'));
// Only show the proxy if the token parameter is correct.
// If we do not compare with a shared secret, someone could very easily
// guess an id for the enrolment.
if (!\enrol_lti\helper::verify_proxy_token($toolid, $token)) {
throw new \moodle_exception('incorrecttoken', 'enrol_lti');
}
$tool = \enrol_lti\helper::get_lti_tool($toolid);
if (!is_enabled_auth('lti')) {
print_error('pluginnotenabled', 'auth', '', get_string('pluginname', 'auth_lti'));
exit();
}
// Check if the enrolment plugin is disabled.
if (!enrol_is_enabled('lti')) {
print_error('enrolisdisabled', 'enrol_lti');
exit();
}
// Check if the enrolment instance is disabled.
if ($tool->status != ENROL_INSTANCE_ENABLED) {
print_error('enrolisdisabled', 'enrol_lti');
exit();
}
$messagetype = required_param('lti_message_type', PARAM_TEXT);
// Only accept proxy registration requests from this endpoint.
if ($messagetype != "ToolProxyRegistrationRequest") {
print_error('invalidrequest', 'enrol_lti');
exit();
}
$toolprovider = new \enrol_lti\tool_provider($toolid);
$toolprovider->handleRequest();
echo $OUTPUT->header();
echo $OUTPUT->footer();

View File

@ -1,4 +1,3 @@
.copy_box {
width: 100%;
max-width: 350px;
max-width: 15vw;
}

View File

@ -0,0 +1,38 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template enrol_lti/proxy_registration
The content to display when editing a tool.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* returnurl The url to return the page to. Can be null if no return url avaliable.
Example context (json):
{}
}}
{{#str}} successfulregistration, enrol_lti {{/str}}
<br/>
{{#returnurl}}
<a href="{{{returnurl}}}">{{#str}} continue {{/str}}</a>
{{/returnurl}}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
{
"@context" : ["http://purl.imsglobal.org/ctx/lti/v2/Profile"],
"@type" : "ToolConsumerProfile",
"lti_version" : "LTI-2p0",
"@id" : "http://alpha.university.edu/resource/ToolConsumerProfile/12434",
"guid" : "b6ffa601-ce1d-4549-9ccf-145670a964d4",
"product_instance" : { },
"service_offered" : [
{
"@type" : "RestService",
"@id" : "http://www.example.com/moodle/lti/resource/ToolConsumerProfile/12434#Result.item",
"endpoint" : "http://www.example.com/moodle/enpoint.php",
"format" : ["application/vnd.ims.lti.v2.toolproxy+json"],
"action" : ["POST"]
}
],
"capability_offered" : [
"Person.name.given",
"Person.name.family",
"Person.email.primary",
"User.image",
"Result.sourcedId",
"basic-lti-launch-request",
"Result.autocreate",
"Result.url"
]
}

View File

@ -93,7 +93,7 @@ class enrol_lti_helper_testcase extends advanced_testcase {
// Set up the LTI enrolment tool.
$data = new stdClass();
$data->maxenrolled = 1;
$tool = $this->create_tool($data);
$tool = $this->getDataGenerator()->create_lti_tool($data);
// Now get all the information we need.
$tool = \enrol_lti\helper::get_lti_tool($tool->id);
@ -122,7 +122,7 @@ class enrol_lti_helper_testcase extends advanced_testcase {
// Set up the LTI enrolment tool.
$data = new stdClass();
$data->enrolstartdate = time() + DAYSECS; // Make sure it is in the future.
$tool = $this->create_tool($data);
$tool = $this->getDataGenerator()->create_lti_tool($data);
// Now get all the information we need.
$tool = \enrol_lti\helper::get_lti_tool($tool->id);
@ -144,7 +144,7 @@ class enrol_lti_helper_testcase extends advanced_testcase {
// Set up the LTI enrolment tool.
$data = new stdClass();
$data->enrolenddate = time() - DAYSECS; // Make sure it is in the past.
$tool = $this->create_tool($data);
$tool = $this->getDataGenerator()->create_lti_tool($data);
// Now get all the information we need.
$tool = \enrol_lti\helper::get_lti_tool($tool->id);
@ -161,22 +161,23 @@ class enrol_lti_helper_testcase extends advanced_testcase {
* Test returning the number of available tools.
*/
public function test_count_lti_tools() {
$generator = $this->getDataGenerator();
// Create two tools belonging to the same course.
$course1 = $this->getDataGenerator()->create_course();
$course1 = $generator->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$this->create_tool($data);
$this->create_tool($data);
$generator->create_lti_tool($data);
$generator->create_lti_tool($data);
// Create two more tools in a separate course.
$course2 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course2->id;
$this->create_tool($data);
$generator->create_lti_tool($data);
// Set the next tool to disabled.
$data->status = ENROL_INSTANCE_DISABLED;
$this->create_tool($data);
$generator->create_lti_tool($data);
// Count all the tools.
$count = \enrol_lti\helper::count_lti_tools();
@ -199,22 +200,23 @@ class enrol_lti_helper_testcase extends advanced_testcase {
* Test returning the list of available tools.
*/
public function test_get_lti_tools() {
$generator = $this->getDataGenerator();
// Create two tools belonging to the same course.
$course1 = $this->getDataGenerator()->create_course();
$course1 = $generator->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->create_tool($data);
$tool2 = $this->create_tool($data);
$tool1 = $generator->create_lti_tool($data);
$tool2 = $generator->create_lti_tool($data);
// Create two more tools in a separate course.
$course2 = $this->getDataGenerator()->create_course();
$course2 = $generator->create_course();
$data = new stdClass();
$data->courseid = $course2->id;
$tool3 = $this->create_tool($data);
$tool3 = $generator->create_lti_tool($data);
// Set the next tool to disabled.
$data->status = ENROL_INSTANCE_DISABLED;
$tool4 = $this->create_tool($data);
$tool4 = $generator->create_lti_tool($data);
// Get all the tools.
$tools = \enrol_lti\helper::get_lti_tools();
@ -254,7 +256,7 @@ class enrol_lti_helper_testcase extends advanced_testcase {
$course1 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->create_tool($data);
$tool1 = $this->getDataGenerator()->create_lti_tool($data);
$id = $tool1->id;
$launchurl = \enrol_lti\helper::get_launch_url($id);
@ -274,10 +276,10 @@ class enrol_lti_helper_testcase extends advanced_testcase {
$course1 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->create_tool($data);
$tool1 = $this->getDataGenerator()->create_lti_tool($data);
$id = $tool1->id;
$token = \enrol_lti\helper::generate_tool_token($id);
$token = \enrol_lti\helper::generate_cartridge_token($id);
$launchurl = \enrol_lti\helper::get_cartridge_url($tool1);
$this->assertEquals('http://www.example.com/moodle/enrol/lti/cartridge.php?id=' . $id . '&amp;token=' . $token,
$launchurl->out());
@ -291,6 +293,36 @@ class enrol_lti_helper_testcase extends advanced_testcase {
$CFG->slasharguments = $slasharguments;
}
/**
* Test getting the cartridge url of a tool.
*/
public function test_get_proxy_url() {
global $CFG;
$slasharguments = $CFG->slasharguments;
$CFG->slasharguments = false;
$course1 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->getDataGenerator()->create_lti_tool($data);
$id = $tool1->id;
$token = \enrol_lti\helper::generate_proxy_token($id);
$launchurl = \enrol_lti\helper::get_proxy_url($tool1);
$this->assertEquals('http://www.example.com/moodle/enrol/lti/proxy.php?id=' . $id . '&amp;token=' . $token,
$launchurl->out());
$CFG->slasharguments = true;
$launchurl = \enrol_lti\helper::get_proxy_url($tool1);
$this->assertEquals('http://www.example.com/moodle/enrol/lti/proxy.php/' . $id . '/' . $token . '/',
$launchurl->out());
$CFG->slasharguments = $slasharguments;
}
/**
* Test getting the name of a tool.
*/
@ -298,7 +330,7 @@ class enrol_lti_helper_testcase extends advanced_testcase {
$course1 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->create_tool($data);
$tool1 = $this->getDataGenerator()->create_lti_tool($data);
$name = \enrol_lti\helper::get_name($tool1);
$this->assertEquals('Course: Test course 1', $name);
@ -312,36 +344,69 @@ class enrol_lti_helper_testcase extends advanced_testcase {
* Test getting the description of a tool.
*/
public function test_get_description() {
$course1 = $this->getDataGenerator()->create_course();
$generator = $this->getDataGenerator();
$course1 = $generator->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->create_tool($data);
$tool1 = $generator->create_lti_tool($data);
$description = \enrol_lti\helper::get_description($tool1);
$this->assertContains('Test course 1 Lorem ipsum dolor sit amet', $description);
$module1 = $this->getDataGenerator()->create_module('assign', array(
$module1 = $generator->create_module('assign', array(
'course' => $course1->id
));
$data = new stdClass();
$data->cmid = $module1->cmid;
$tool2 = $this->create_tool($data);
$tool2 = $generator->create_lti_tool($data);
$description = \enrol_lti\helper::get_description($tool2);
$this->assertContains('Test assign 1', $description);
}
/**
* Test verifying a tool token.
* Test getting the icon of a tool.
*/
public function test_verify_tool_token() {
public function test_get_icon() {
global $CFG;
$course1 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->create_tool($data);
$tool = $this->getDataGenerator()->create_lti_tool($data);
$token = \enrol_lti\helper::generate_tool_token($tool1->id);
$this->assertTrue(\enrol_lti\helper::verify_tool_token($tool1->id, $token));
$this->assertFalse(\enrol_lti\helper::verify_tool_token($tool1->id, 'incorrect token!'));
$icon = \enrol_lti\helper::get_icon($tool);
$icon = $icon->out();
// Only local icons are supported by the LTI framework.
$this->assertContains($CFG->wwwroot, $icon);
}
/**
* Test verifying a cartridge token.
*/
public function test_verify_cartridge_token() {
$course1 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->getDataGenerator()->create_lti_tool($data);
$token = \enrol_lti\helper::generate_cartridge_token($tool1->id);
$this->assertTrue(\enrol_lti\helper::verify_cartridge_token($tool1->id, $token));
$this->assertFalse(\enrol_lti\helper::verify_cartridge_token($tool1->id, 'incorrect token!'));
}
/**
* Test verifying a proxy token.
*/
public function test_verify_proxy_token() {
$course1 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->getDataGenerator()->create_lti_tool($data);
$token = \enrol_lti\helper::generate_proxy_token($tool1->id);
$this->assertTrue(\enrol_lti\helper::verify_proxy_token($tool1->id, $token));
$this->assertFalse(\enrol_lti\helper::verify_proxy_token($tool1->id, 'incorrect token!'));
}
/**
@ -464,55 +529,11 @@ class enrol_lti_helper_testcase extends advanced_testcase {
$course1 = $this->getDataGenerator()->create_course();
$data = new stdClass();
$data->courseid = $course1->id;
$tool1 = $this->create_tool($data);
$tool1 = $this->getDataGenerator()->create_lti_tool($data);
$cartridge = \enrol_lti\helper::create_cartridge($tool1->id);
$this->assertContains('<blti:title>Test LTI</blti:title>', $cartridge);
$this->assertContains("<blti:icon>$CFG->wwwroot/theme/image.php/_s/clean/theme/1/favicon</blti:icon>", $cartridge);
$this->assertContains("<blti:launch_url>$CFG->wwwroot/enrol/lti/tool.php?id=$tool1->id</blti:launch_url>", $cartridge);
}
/**
* Helper function used to create a tool.
*
* @param array $data
* @return stdClass the tool
*/
protected function create_tool($data = array()) {
global $DB;
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
// Create a course if no course id was specified.
if (empty($data->courseid)) {
$course = $this->getDataGenerator()->create_course();
$data->courseid = $course->id;
} else {
$course = get_course($data->courseid);
}
if (!empty($data->cmid)) {
$data->contextid = context_module::instance($data->cmid)->id;
} else {
$data->contextid = context_course::instance($data->courseid)->id;
}
// Set it to enabled if no status was specified.
if (!isset($data->status)) {
$data->status = ENROL_INSTANCE_ENABLED;
}
// Add some extra necessary fields to the data.
$data->name = 'Test LTI';
$data->roleinstructor = $studentrole->id;
$data->rolelearner = $teacherrole->id;
// Get the enrol LTI plugin.
$enrolplugin = enrol_get_plugin('lti');
$instanceid = $enrolplugin->add_instance($course, (array) $data);
// Get the tool associated with this instance.
return $DB->get_record('enrol_lti_tools', array('enrolid' => $instanceid));
}
}

View File

@ -0,0 +1,120 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tests for the enrol_lti_plugin class.
*
* @package enrol_lti
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use enrol_lti\data_connector;
use enrol_lti\tool_provider;
use IMSGlobal\LTI\ToolProvider\ResourceLink;
use IMSGlobal\LTI\ToolProvider\ToolConsumer;
use IMSGlobal\LTI\ToolProvider\ToolProvider;
use IMSGlobal\LTI\ToolProvider\User;
defined('MOODLE_INTERNAL') || die();
/**
* Tests for the enrol_lti_plugin class.
*
* @package enrol_lti
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class enrol_lti_testcase extends advanced_testcase {
/**
* Test set up.
*
* This is executed before running any tests in this file.
*/
public function setUp() {
$this->resetAfterTest();
$this->setAdminUser();
}
/**
* Test for enrol_lti_plugin::delete_instance().
*/
public function test_delete_instance() {
global $DB;
// Create tool enrolment instance.
$data = new stdClass();
$data->enrolstartdate = time();
$data->secret = 'secret';
$tool = $this->getDataGenerator()->create_lti_tool($data);
// Create consumer and related data.
$dataconnector = new data_connector();
$consumer = new ToolConsumer('testkey', $dataconnector);
$consumer->secret = $tool->secret;
$consumer->ltiVersion = ToolProvider::LTI_VERSION1;
$consumer->name = 'TEST CONSUMER NAME';
$consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
$consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
$consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
$consumer->enabled = true;
$consumer->protected = true;
$consumer->save();
$resourcelink = ResourceLink::fromConsumer($consumer, 'testresourcelinkid');
$resourcelink->save();
$ltiuser = User::fromResourceLink($resourcelink, '');
$ltiuser->ltiResultSourcedId = 'testLtiResultSourcedId';
$ltiuser->ltiUserId = 'testuserid';
$ltiuser->email = 'user1@example.com';
$ltiuser->save();
$tp = new tool_provider($tool->id);
$tp->user = $ltiuser;
$tp->resourceLink = $resourcelink;
$tp->consumer = $consumer;
$tp->map_tool_to_consumer();
$mappingparams = [
'toolid' => $tool->id,
'consumerid' => $tp->consumer->getRecordId()
];
// Check first that the related records exist.
$this->assertTrue($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams));
$this->assertTrue($DB->record_exists('enrol_lti_lti2_consumer', [ 'id' => $consumer->getRecordId() ]));
$this->assertTrue($DB->record_exists('enrol_lti_lti2_resource_link', [ 'id' => $resourcelink->getRecordId() ]));
$this->assertTrue($DB->record_exists('enrol_lti_lti2_user_result', [ 'id' => $ltiuser->getRecordId() ]));
// Perform deletion.
$enrollti = new enrol_lti_plugin();
$instance = $DB->get_record('enrol', ['id' => $tool->enrolid]);
$enrollti->delete_instance($instance);
// Check that the related records have been deleted.
$this->assertFalse($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams));
$this->assertFalse($DB->record_exists('enrol_lti_lti2_consumer', [ 'id' => $consumer->getRecordId() ]));
$this->assertFalse($DB->record_exists('enrol_lti_lti2_resource_link', [ 'id' => $resourcelink->getRecordId() ]));
$this->assertFalse($DB->record_exists('enrol_lti_lti2_user_result', [ 'id' => $ltiuser->getRecordId() ]));
// Check that the enrolled users and the tool instance has been deleted.
$this->assertFalse($DB->record_exists('enrol_lti_users', [ 'toolid' => $tool->id ]));
$this->assertFalse($DB->record_exists('enrol_lti_tools', [ 'id' => $tool->id ]));
$this->assertFalse($DB->record_exists('enrol', [ 'id' => $instance->id ]));
}
}

View File

@ -0,0 +1,671 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tests for the tool_provider class.
*
* @package enrol_lti
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\session\manager;
use enrol_lti\data_connector;
use enrol_lti\helper;
use enrol_lti\tool_provider;
use IMSGlobal\LTI\HTTPMessage;
use IMSGlobal\LTI\ToolProvider\ResourceLink;
use IMSGlobal\LTI\ToolProvider\ToolConsumer;
use IMSGlobal\LTI\ToolProvider\ToolProvider;
use IMSGlobal\LTI\ToolProvider\User;
defined('MOODLE_INTERNAL') || die();
/**
* Tests for the tool_provider class.
*
* @package enrol_lti
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_provider_testcase extends advanced_testcase {
/**
* @var stdClass $tool The LTI tool.
*/
protected $tool;
/**
* Test set up.
*
* This is executed before running any tests in this file.
*/
public function setUp() {
global $SESSION;
$this->resetAfterTest();
manager::init_empty_session();
// Set this user as the admin.
$this->setAdminUser();
$data = new stdClass();
$data->enrolstartdate = time();
$data->secret = 'secret';
$toolrecord = $this->getDataGenerator()->create_lti_tool($data);
$this->tool = helper::get_lti_tool($toolrecord->id);
$SESSION->notifications = [];
}
/**
* Passing non-existent tool ID.
*/
public function test_constructor_with_non_existent_tool() {
$this->expectException('dml_exception');
new tool_provider(1);
}
/**
* Constructor test.
*/
public function test_constructor() {
global $CFG, $SITE;
$tool = $this->tool;
$tp = new tool_provider($tool->id);
$this->assertNull($tp->consumer);
$this->assertNull($tp->returnUrl);
$this->assertNull($tp->resourceLink);
$this->assertNull($tp->context);
$this->assertNotNull($tp->dataConnector);
$this->assertEquals('', $tp->defaultEmail);
$this->assertEquals(ToolProvider::ID_SCOPE_ID_ONLY, $tp->idScope);
$this->assertFalse($tp->allowSharing);
$this->assertEquals(ToolProvider::CONNECTION_ERROR_MESSAGE, $tp->message);
$this->assertNull($tp->reason);
$this->assertEmpty($tp->details);
$this->assertEquals($CFG->wwwroot, $tp->baseUrl);
$this->assertNotNull($tp->vendor);
$this->assertEquals($SITE->shortname, $tp->vendor->id);
$this->assertEquals($SITE->fullname, $tp->vendor->name);
$this->assertEquals($SITE->summary, $tp->vendor->description);
$token = helper::generate_proxy_token($tool->id);
$name = helper::get_name($tool);
$description = helper::get_description($tool);
$this->assertNotNull($tp->product);
$this->assertEquals($token, $tp->product->id);
$this->assertEquals($name, $tp->product->name);
$this->assertEquals($description, $tp->product->description);
$this->assertNotNull($tp->requiredServices);
$this->assertEmpty($tp->optionalServices);
$this->assertNotNull($tp->resourceHandlers);
}
/**
* Test for handle request.
*/
public function test_handle_request_no_request_data() {
$tool = $this->tool;
$tp = new tool_provider($tool->id);
// Tool provider object should have been created fine. OK flag should be fine for now.
$this->assertTrue($tp->ok);
// Call handleRequest but suppress output.
ob_start();
$tp->handleRequest();
ob_end_clean();
// There's basically no request data submitted so OK flag should turn out false.
$this->assertFalse($tp->ok);
}
/**
* Test for tool_provider::onError().
*/
public function test_on_error() {
$tool = $this->tool;
$tp = new dummy_tool_provider($tool->id);
$message = "THIS IS AN ERROR!";
$tp->message = $message;
$tp->onError();
$errormessage = get_string('failedrequest', 'enrol_lti', ['reason' => $message]);
$this->assertContains($errormessage, $tp->get_error_output());
}
/**
* Test for tool_provider::onRegister() with no tool consumer set.
*/
public function test_on_register_no_consumer() {
$tool = $this->tool;
$tp = new dummy_tool_provider($tool->id);
$tp->onRegister();
$this->assertFalse($tp->ok);
$this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message);
}
/**
* Test for tool_provider::onRegister() without return URL.
*/
public function test_on_register_no_return_url() {
$tool = $this->tool;
$dataconnector = new data_connector();
$consumer = new ToolConsumer('testkey', $dataconnector);
$consumer->ltiVersion = ToolProvider::LTI_VERSION2;
$consumer->secret = $tool->secret;
$consumer->name = 'TEST CONSUMER NAME';
$consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
$consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
$consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
$consumer->enabled = true;
$consumer->protected = true;
$consumer->save();
$tp = new dummy_tool_provider($tool->id);
$tp->consumer = $consumer;
$tp->onRegister();
$this->assertFalse($tp->ok);
$this->assertEquals(get_string('returnurlnotset', 'enrol_lti'), $tp->message);
}
/**
* Test for tool_provider::onRegister() when registration fails.
*/
public function test_on_register_failed() {
global $CFG;
$tool = $this->tool;
$dataconnector = new data_connector();
$consumer = new dummy_tool_consumer('testkey', $dataconnector);
$consumer->ltiVersion = ToolProvider::LTI_VERSION2;
$consumer->secret = $tool->secret;
$consumer->name = 'TEST CONSUMER NAME';
$consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
$consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
$consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
$consumer->enabled = true;
$consumer->protected = true;
$profilejson = file_get_contents(__DIR__ . '/fixtures/tool_consumer_profile.json');
$consumer->profile = json_decode($profilejson);
$consumer->save();
$tp = new dummy_tool_provider($tool->id);
$tp->consumer = $consumer;
$tp->returnUrl = $CFG->wwwroot;
$tp->onRegister();
// The OK flag will be false.
$this->assertFalse($tp->ok);
// Check message.
$this->assertEquals(get_string('couldnotestablishproxy', 'enrol_lti'), $tp->message);
}
/**
* Test for tool_provider::onRegister() when registration succeeds.
*/
public function test_on_register() {
global $CFG, $DB;
$tool = $this->tool;
$dataconnector = new data_connector();
$consumer = new dummy_tool_consumer('testkey', $dataconnector, false, true);
$consumer->ltiVersion = ToolProvider::LTI_VERSION2;
$consumer->secret = $tool->secret;
$consumer->name = 'TEST CONSUMER NAME';
$consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
$consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
$consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
$consumer->enabled = true;
$consumer->protected = true;
$profilejson = file_get_contents(__DIR__ . '/fixtures/tool_consumer_profile.json');
$consumer->profile = json_decode($profilejson);
$consumer->save();
$tp = new dummy_tool_provider($tool->id);
$tp->consumer = $consumer;
$tp->returnUrl = $CFG->wwwroot;
// Capture output of onLaunch() method and save it as a string.
ob_start();
$tp->onRegister();
$output = ob_get_clean();
$successmessage = get_string('successfulregistration', 'enrol_lti');
// Check output contents. Confirm that it has the success message and return URL.
$this->assertContains($successmessage, $output);
$this->assertContains($tp->returnUrl, $output);
// The OK flag will be true on successful registration.
$this->assertTrue($tp->ok);
// Check tool provider message.
$this->assertEquals($successmessage, $tp->message);
// Check published tool and tool consumer mapping.
$mappingparams = [
'toolid' => $tool->id,
'consumerid' => $tp->consumer->getRecordId()
];
$this->assertTrue($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams));
}
/**
* Test for tool_provider::onLaunch().
*/
public function test_on_launch_no_frame_embedding() {
$tp = $this->build_dummy_tp();
// Capture output of onLaunch() method and save it as a string.
ob_start();
// Suppress session header errors.
@$tp->onLaunch();
$output = ob_get_clean();
$this->assertContains(get_string('frameembeddingnotenabled', 'enrol_lti'), $output);
}
/**
* Test for tool_provider::onLaunch().
*/
public function test_on_launch_with_frame_embedding() {
global $CFG;
$CFG->allowframembedding = true;
$tp = $this->build_dummy_tp();
// If redirect was called here, we will encounter an 'unsupported redirect error'.
// We just want to verify that redirect() was called if frame embedding is allowed.
$this->expectException('moodle_exception');
// Suppress session header errors.
@$tp->onLaunch();
}
/**
* Test for tool_provider::onLaunch() with invalid secret and no tool proxy (for LTI 1 launches).
*/
public function test_on_launch_with_invalid_secret_and_no_proxy() {
$tp = $this->build_dummy_tp('badsecret');
// Suppress session header errors.
@$tp->onLaunch();
$this->assertFalse($tp->ok);
$this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message);
}
/**
* Test for tool_provider::onLaunch() with invalid launch URL.
*/
public function test_on_launch_proxy_with_invalid_launch_url() {
$proxy = [
'tool_profile' => [
'resource_handler' => [
[
'message' => [
[
'message_type' => 'basic-lti-launch-request',
'path' => '/enrol/lti/tool.php'
]
]
]
]
]
];
$tp = $this->build_dummy_tp($this->tool->secret, $proxy);
// Suppress session header errors.
@$tp->onLaunch();
$this->assertFalse($tp->ok);
$this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message);
}
/**
* Test for tool_provider::onLaunch() with invalid launch URL.
*/
public function test_on_launch_proxy_with_valid_launch_url() {
$tool = $this->tool;
$proxy = [
'tool_profile' => [
'resource_handler' => [
[
'message' => [
[
'message_type' => 'basic-lti-launch-request',
'path' => '/enrol/lti/tool.php?id=' . $tool->id
]
]
]
]
]
];
$tp = $this->build_dummy_tp($this->tool->secret, $proxy);
// Capture output of onLaunch() method and save it as a string.
ob_start();
// Suppress session header errors.
@$tp->onLaunch();
$output = ob_get_clean();
$this->assertTrue($tp->ok);
$this->assertEquals(get_string('success'), $tp->message);
$this->assertContains(get_string('frameembeddingnotenabled', 'enrol_lti'), $output);
}
/**
* Test for tool_provider::onLaunch() for a request with message type other than basic-lti-launch-request.
*/
public function test_on_launch_proxy_with_invalid_message_type() {
$tool = $this->tool;
$proxy = [
'tool_profile' => [
'resource_handler' => [
[
'message' => [
[
'message_type' => 'ContentItemSelectionRequest',
'path' => '/enrol/lti/tool.php?id=' . $tool->id
]
]
]
]
]
];
$tp = $this->build_dummy_tp($this->tool->secret, $proxy);
// Suppress session header errors.
@$tp->onLaunch();
$this->assertFalse($tp->ok);
$this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message);
}
/**
* Test for tool_provider::onLaunch() to verify that a user image can be set from the resource link's custom_user_image setting.
*/
public function test_on_launch_with_user_image_from_resource_link() {
global $DB;
$userimageurl = $this->getExternalTestFileUrl('test.jpg');
$resourcelinksettings = [
'custom_user_image' => $userimageurl
];
$tp = $this->build_dummy_tp($this->tool->secret, null, $resourcelinksettings);
// Suppress output and session header errors.
ob_start();
@$tp->onLaunch();
ob_end_clean();
$this->assertEquals($userimageurl, $tp->resourceLink->getSetting('custom_user_image'));
$username = helper::create_username($tp->consumer->getKey(), $tp->user->ltiUserId);
$user = $DB->get_record('user', ['username' => $username]);
// User was found.
$this->assertNotFalse($user);
// User picture was set.
$this->assertNotEmpty($user->picture);
}
/**
* Test for tool_provider::onLaunch() to verify that a LTI user has been enrolled.
*/
public function test_on_launch_user_enrolment() {
global $DB;
$tp = $this->build_dummy_tp($this->tool->secret);
// Suppress output and session header errors.
ob_start();
@$tp->onLaunch();
ob_end_clean();
$username = helper::create_username($tp->consumer->getKey(), $tp->user->ltiUserId);
$user = $DB->get_record('user', ['username' => $username]);
// User was found.
$this->assertNotFalse($user);
// User picture was not set.
$this->assertEmpty($user->picture);
// Check user enrolment.
$enrolled = $DB->record_exists('user_enrolments', ['enrolid' => $this->tool->enrolid, 'userid' => $user->id]);
$this->assertTrue($enrolled);
}
/**
* Test for tool_provider::onLaunch() when the consumer object has not been set.
*/
public function test_on_launch_no_consumer() {
global $DB;
$tool = $this->tool;
$tp = new dummy_tool_provider($tool->id);
$tp->onLaunch();
$this->assertFalse($tp->ok);
$this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message);
// Check published tool and tool consumer has not yet been mapped due to failure.
$mappingparams = [
'toolid' => $tool->id
];
$this->assertFalse($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams));
}
/**
* Test for tool_provider::onLaunch() when we have a non-existent consumer data.
*/
public function test_on_launch_invalid_consumer() {
$tool = $this->tool;
$dataconnector = new data_connector();
// Build consumer object but don't save it.
$consumer = new dummy_tool_consumer('testkey', $dataconnector);
$tp = new dummy_tool_provider($tool->id);
$tp->consumer = $consumer;
$tp->onLaunch();
$this->assertFalse($tp->ok);
$this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message);
}
/**
* Test for tool_provider::map_tool_to_consumer().
*/
public function test_map_tool_to_consumer() {
global $DB;
$tp = $this->build_dummy_tp();
$tp->map_tool_to_consumer();
// Check published tool and tool consumer mapping.
$mappingparams = [
'toolid' => $this->tool->id,
'consumerid' => $tp->consumer->getRecordId()
];
$this->assertTrue($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams));
}
/**
* Test for tool_provider::map_tool_to_consumer().
*/
public function test_map_tool_to_consumer_no_consumer() {
$tp = new dummy_tool_provider($this->tool->id);
$this->expectException('moodle_exception');
$tp->map_tool_to_consumer();
}
/**
* Builds a dummy tool provider object.
*
* @param string $secret Consumer secret.
* @param array|stdClass $proxy Tool proxy data.
* @param null $resourcelinksettings Key-value array for resource link settings.
* @return dummy_tool_provider
*/
protected function build_dummy_tp($secret = null, $proxy = null, $resourcelinksettings = null) {
$tool = $this->tool;
$dataconnector = new data_connector();
$consumer = new ToolConsumer('testkey', $dataconnector);
$ltiversion = ToolProvider::LTI_VERSION2;
if ($secret === null && $proxy === null) {
$consumer->secret = $tool->secret;
$ltiversion = ToolProvider::LTI_VERSION1;
} else {
$consumer->secret = $secret;
}
$consumer->ltiVersion = $ltiversion;
$consumer->name = 'TEST CONSUMER NAME';
$consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
$consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
$consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
$consumer->enabled = true;
$consumer->protected = true;
if ($proxy !== null) {
$consumer->toolProxy = json_encode($proxy);
}
$consumer->save();
$resourcelink = ResourceLink::fromConsumer($consumer, 'testresourcelinkid');
if (!empty($resourcelinksettings)) {
foreach ($resourcelinksettings as $setting => $value) {
$resourcelink->setSetting($setting, $value);
}
}
$resourcelink->save();
$ltiuser = User::fromResourceLink($resourcelink, '');
$ltiuser->ltiResultSourcedId = 'testLtiResultSourcedId';
$ltiuser->ltiUserId = 'testuserid';
$ltiuser->email = 'user1@example.com';
$ltiuser->save();
$tp = new dummy_tool_provider($tool->id);
$tp->user = $ltiuser;
$tp->resourceLink = $resourcelink;
$tp->consumer = $consumer;
return $tp;
}
}
/**
* Class dummy_tool_provider.
*
* A class that extends tool_provider so that we can expose the protected methods that we have overridden.
*
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dummy_tool_provider extends tool_provider {
/**
* Exposes tool_provider::onError().
*/
public function onError() {
parent::onError();
}
/**
* Exposes tool_provider::onLaunch().
*/
public function onLaunch() {
parent::onLaunch();
}
/**
* Exposes tool_provider::onRegister().
*/
public function onRegister() {
parent::onRegister();
}
/**
* Expose protected variable errorOutput.
*
* @return string
*/
public function get_error_output() {
return $this->errorOutput;
}
}
/**
* Class dummy_tool_consumer
*
* A class that extends ToolConsumer in order to override and simulate sending and receiving data to tool consumer endpoint.
*
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dummy_tool_consumer extends ToolConsumer {
/**
* @var bool Flag to indicate whether to send an OK response or a failed response.
*/
protected $success = false;
/**
* dummy_tool_consumer constructor.
*
* @param null|string $key
* @param mixed|null $dataconnector
* @param bool $autoenable
* @param bool $success
*/
public function __construct($key = null, $dataconnector = null, $autoenable = false, $success = false) {
parent::__construct($key, $dataconnector, $autoenable);
$this->success = $success;
}
/**
* Override ToolConsumer::doServiceRequest() to simulate sending/receiving data to and from the tool consumer.
*
* @param object $service
* @param string $method
* @param string $format
* @param mixed $data
* @return HTTPMessage
*/
public function doServiceRequest($service, $method, $format, $data) {
$response = (object)['tool_proxy_guid' => 1];
$header = ToolConsumer::addSignature($service->endpoint, $this->getKey(), $this->secret, $data, $method, $format);
$http = new HTTPMessage($service->endpoint, $method, $data, $header);
if ($this->success) {
$http->responseJson = $response;
$http->ok = true;
$http->status = 201;
}
return $http;
}
}

View File

@ -23,195 +23,73 @@
*/
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/user/lib.php');
require_once($CFG->dirroot . '/enrol/lti/ims-blti/blti.php');
$toolid = required_param('id', PARAM_INT);
$PAGE->set_context(context_system::instance());
$url = new moodle_url('/enrol/lti/tool.php');
$PAGE->set_url($url);
$PAGE->set_pagelayout('popup');
$PAGE->set_title(get_string('opentool', 'enrol_lti'));
// Get the tool.
$tool = \enrol_lti\helper::get_lti_tool($toolid);
// Create the BLTI request.
$ltirequest = new BLTI($tool->secret, false, false);
// Correct launch request.
if ($ltirequest->valid) {
// Check if the authentication plugin is disabled.
if (!is_enabled_auth('lti')) {
print_error('pluginnotenabled', 'auth', '', get_string('pluginname', 'auth_lti'));
exit();
}
// Check if the enrolment plugin is disabled.
if (!enrol_is_enabled('lti')) {
print_error('enrolisdisabled', 'enrol_lti');
exit();
}
// Check if the enrolment instance is disabled.
if ($tool->status != ENROL_INSTANCE_ENABLED) {
print_error('enrolisdisabled', 'enrol_lti');
exit();
}
// Before we do anything check that the context is valid.
$context = context::instance_by_id($tool->contextid);
// Set the user data.
$user = new stdClass();
$user->username = \enrol_lti\helper::create_username($ltirequest->info['oauth_consumer_key'], $ltirequest->info['user_id']);
if (!empty($ltirequest->info['lis_person_name_given'])) {
$user->firstname = $ltirequest->info['lis_person_name_given'];
} else {
$user->firstname = $ltirequest->info['user_id'];
}
if (!empty($ltirequest->info['lis_person_name_family'])) {
$user->lastname = $ltirequest->info['lis_person_name_family'];
} else {
$user->lastname = $ltirequest->info['context_id'];
}
$user->email = \core_user::clean_field($ltirequest->getUserEmail(), 'email');
// Get the user data from the LTI consumer.
$user = \enrol_lti\helper::assign_user_tool_data($tool, $user);
// Check if the user exists.
if (!$dbuser = $DB->get_record('user', array('username' => $user->username, 'deleted' => 0))) {
// If the email was stripped/not set then fill it with a default one. This
// stops the user from being redirected to edit their profile page.
if (empty($user->email)) {
$user->email = $user->username . "@example.com";
}
$user->auth = 'lti';
$user->id = user_create_user($user);
// Get the updated user record.
$user = $DB->get_record('user', array('id' => $user->id));
} else {
if (\enrol_lti\helper::user_match($user, $dbuser)) {
$user = $dbuser;
} else {
// If email is empty remove it, so we don't update the user with an empty email.
if (empty($user->email)) {
unset($user->email);
}
$user->id = $dbuser->id;
user_update_user($user);
// Get the updated user record.
$user = $DB->get_record('user', array('id' => $user->id));
}
}
// Update user image.
$image = false;
if (!empty($ltirequest->info['user_image'])) {
$image = $ltirequest->info['user_image'];
} else if (!empty($ltirequest->info['custom_user_image'])) {
$image = $ltirequest->info['custom_user_image'];
}
// Check if there is an image to process.
if ($image) {
\enrol_lti\helper::update_user_profile_image($user->id, $image);
}
// Check if we are an instructor.
$isinstructor = $ltirequest->isInstructor();
if ($context->contextlevel == CONTEXT_COURSE) {
$courseid = $context->instanceid;
$urltogo = new moodle_url('/course/view.php', array('id' => $courseid));
// May still be set from previous session, so unset it.
unset($SESSION->forcepagelayout);
} else if ($context->contextlevel == CONTEXT_MODULE) {
$cmid = $context->instanceid;
$cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST);
$urltogo = new moodle_url('/mod/' . $cm->modname . '/view.php', array('id' => $cm->id));
// If we are a student in the course module context we do not want to display blocks.
if (!$isinstructor) {
// Force the page layout.
$SESSION->forcepagelayout = 'embedded';
} else {
// May still be set from previous session, so unset it.
unset($SESSION->forcepagelayout);
}
} else {
print_error('invalidcontext');
exit();
}
// Enrol the user in the course with no role.
$result = \enrol_lti\helper::enrol_user($tool, $user->id);
// Display an error, if there is one.
if ($result !== \enrol_lti\helper::ENROLMENT_SUCCESSFUL) {
print_error($result, 'enrol_lti');
exit();
}
// Give the user the role in the given context.
$roleid = $isinstructor ? $tool->roleinstructor : $tool->rolelearner;
role_assign($roleid, $user->id, $tool->contextid);
// Login user.
$sourceid = (!empty($ltirequest->info['lis_result_sourcedid'])) ? $ltirequest->info['lis_result_sourcedid'] : '';
$serviceurl = (!empty($ltirequest->info['lis_outcome_service_url'])) ? $ltirequest->info['lis_outcome_service_url'] : '';
// Check if we have recorded this user before.
if ($userlog = $DB->get_record('enrol_lti_users', array('toolid' => $tool->id, 'userid' => $user->id))) {
if ($userlog->sourceid != $sourceid) {
$userlog->sourceid = $sourceid;
}
if ($userlog->serviceurl != $serviceurl) {
$userlog->serviceurl = $serviceurl;
}
$userlog->lastaccess = time();
$DB->update_record('enrol_lti_users', $userlog);
} else {
// Add the user details so we can use it later when syncing grades and members.
$userlog = new stdClass();
$userlog->userid = $user->id;
$userlog->toolid = $tool->id;
$userlog->serviceurl = $serviceurl;
$userlog->sourceid = $sourceid;
$userlog->consumerkey = $ltirequest->info['oauth_consumer_key'];
$userlog->consumersecret = $tool->secret;
$userlog->lastgrade = 0;
$userlog->lastaccess = time();
$userlog->timecreated = time();
if (!empty($ltirequest->info['ext_ims_lis_memberships_url'])) {
$userlog->membershipsurl = $ltirequest->info['ext_ims_lis_memberships_url'];
} else {
$userlog->membershipsurl = '';
}
if (!empty($ltirequest->info['ext_ims_lis_memberships_id'])) {
$userlog->membershipsid = $ltirequest->info['ext_ims_lis_memberships_id'];
} else {
$userlog->membershipsid = '';
}
$DB->insert_record('enrol_lti_users', $userlog);
}
// Finalise the user log in.
complete_user_login($user);
if (empty($CFG->allowframembedding)) {
// Provide an alternative link.
$stropentool = get_string('opentool', 'enrol_lti');
echo html_writer::tag('p', get_string('frameembeddingnotenabled', 'enrol_lti'));
echo html_writer::link($urltogo, $stropentool, array('target' => '_blank'));
} else {
// All done, redirect the user to where they want to go.
redirect($urltogo);
}
} else {
echo $ltirequest->message;
// Check if the authentication plugin is disabled.
if (!is_enabled_auth('lti')) {
print_error('pluginnotenabled', 'auth', '', get_string('pluginname', 'auth_lti'));
exit();
}
// Check if the enrolment plugin is disabled.
if (!enrol_is_enabled('lti')) {
print_error('enrolisdisabled', 'enrol_lti');
exit();
}
// Check if the enrolment instance is disabled.
if ($tool->status != ENROL_INSTANCE_ENABLED) {
print_error('enrolisdisabled', 'enrol_lti');
exit();
}
$consumerkey = required_param('oauth_consumer_key', PARAM_TEXT);
$ltiversion = optional_param('lti_version', null, PARAM_TEXT);
$messagetype = required_param('lti_message_type', PARAM_TEXT);
// Only accept launch requests from this endpoint.
if ($messagetype != "basic-lti-launch-request") {
print_error('invalidrequest', 'enrol_lti');
exit();
}
// Initialise tool provider.
$toolprovider = new \enrol_lti\tool_provider($toolid);
// Special handling for LTIv1 launch requests.
if ($ltiversion === \IMSGlobal\LTI\ToolProvider\ToolProvider::LTI_VERSION1) {
$dataconnector = new \enrol_lti\data_connector();
$consumer = new \IMSGlobal\LTI\ToolProvider\ToolConsumer($consumerkey, $dataconnector);
// Check if the consumer has already been registered to the enrol_lti_lti2_consumer table. Register if necessary.
$consumer->ltiVersion = \IMSGlobal\LTI\ToolProvider\ToolProvider::LTI_VERSION1;
// For LTIv1, set the tool secret as the consumer secret.
$consumer->secret = $tool->secret;
$consumer->name = optional_param('tool_consumer_instance_name', null, PARAM_TEXT);
$consumer->consumerName = $consumer->name;
$consumer->consumerGuid = optional_param('tool_consumer_instance_guid', null, PARAM_TEXT);
$consumer->consumerVersion = optional_param('tool_consumer_info_version', null, PARAM_TEXT);
$consumer->enabled = true;
$consumer->protected = true;
$consumer->save();
// Set consumer to tool provider.
$toolprovider->consumer = $consumer;
// Map tool consumer and published tool, if necessary.
$toolprovider->map_tool_to_consumer();
}
// Handle the request.
$toolprovider->handleRequest();
echo $OUTPUT->header();
echo $OUTPUT->footer();

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2016052301; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2016052303; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2016051900; // Requires this Moodle version (3.1)
$plugin->component = 'enrol_lti'; // Full name of the plugin (used for diagnostics).

View File

@ -82,6 +82,7 @@ class core_component {
'Box\\Spout' => 'lib/spout/src/Spout',
'MatthiasMullie\\Minify' => 'lib/minify/matthiasmullie-minify/src/',
'MatthiasMullie\\PathConverter' => 'lib/minify/matthiasmullie-pathconverter/src/',
'IMSGlobal\LTI' => 'lib/ltiprovider/src',
);
/**

201
lib/ltiprovider/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
lib/ltiprovider/README.md Normal file
View File

@ -0,0 +1,21 @@
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.
The wiki area of this repository contains [documentation](https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/wiki) for this library. The [rating LTI application](https://github.com/IMSGlobal/LTI-Sample-Tool-Provider-PHP) is based on this library to further illustrate how it can be used.
&copy; 2016 IMS Global Learning Consortium Inc. All Rights Reserved. Trademark Policy - (www.imsglobal.org/trademarks)
<sup><sub>Learning Tools Interoperability and LTI are registered trademarks of IMS Global Learning Consortium Inc.</sub></sup>

View File

@ -0,0 +1,43 @@
LTI Tool Provider Library PHP
=============================
No changes from the upstream version have been made, it is recommended by upstream
to install these depdencies via composer - but the composer installation is bundled
with an autoloader so it's better to do it manually.
Information
-----------
URL: https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/wiki
License: Apache License, Version 2.0
Installation
------------
1) Download the latest version of the provider library
wget https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/archive/3.0.3.zip
2) Unzip the archive
unzip 3.0.3.zip
3) Move the source code directory into place
mv LTI-Tool-Provider-Library-PHP-3.0.3/* lib/ltiprovider/
4) Run unit tests on enrol_lti_testsuite
Upgrading Notes
---------------
Check if there are any new changes to the database schema. To do this, view the logs
since the last release for the data connector base class and the mysql data connector.
https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/compare/3.0.2...3.0.3
src/ToolProvider/DataConnector/DataConnector.php
src/ToolProvider/DataConnector/DataConnector_mysql.php
In case of any changes we may need to update
enrol/lti/classes/data_connector.php
enrol/lti/db/install.xml
enrol/lti/db/upgrade.php

View File

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

View File

@ -0,0 +1,27 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Consumer
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthConsumer {
public $key;
public $secret;
function __construct($key, $secret, $callback_url=NULL) {
$this->key = $key;
$this->secret = $secret;
$this->callback_url = $callback_url;
}
function __toString() {
return "OAuthConsumer[key=$this->key,secret=$this->secret]";
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Data Store
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthDataStore {
function lookup_consumer($consumer_key) {
// implement me
}
function lookup_token($consumer, $token_type, $token) {
// implement me
}
function lookup_nonce($consumer, $token, $nonce, $timestamp) {
// implement me
}
function new_request_token($consumer, $callback = null) {
// return a new token attached to this consumer
}
function new_access_token($token, $consumer, $verifier = null) {
// return a new access token attached to this consumer
// for the user associated with this token if the request token
// is authorized
// should also invalidate the request token
}
}

View File

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

View File

@ -0,0 +1,290 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Request
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthRequest {
protected $parameters;
protected $http_method;
protected $http_url;
// for debug purposes
public $base_string;
public static $version = '1.0';
public static $POST_INPUT = 'php://input';
function __construct($http_method, $http_url, $parameters = null) {
$parameters = ($parameters) ? $parameters : array();
$parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
$this->parameters = $parameters;
$this->http_method = $http_method;
$this->http_url = $http_url;
}
/**
* attempt to build up a request from what was passed to the server
*/
public static function from_request($http_method = null, $http_url = null, $parameters = null) {
$scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
? 'http'
: 'https';
$http_url = ($http_url) ? $http_url : $scheme .
'://' . $_SERVER['SERVER_NAME'] .
':' .
$_SERVER['SERVER_PORT'] .
$_SERVER['REQUEST_URI'];
$http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
// We weren't handed any parameters, so let's find the ones relevant to
// this request.
// If you run XML-RPC or similar you should use this to provide your own
// parsed parameter-list
if (!$parameters) {
// Find request headers
$request_headers = OAuthUtil::get_headers();
// Parse the query-string to find GET parameters
if (isset($_SERVER['QUERY_STRING'])) {
$parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
} else {
$parameters = array();
}
// It's a POST request of the proper content-type, so parse POST
// parameters and add those overriding any duplicates from GET
if ($http_method == "POST"
&& isset($request_headers['Content-Type'])
&& strstr($request_headers['Content-Type'], 'application/x-www-form-urlencoded')) {
$post_data = OAuthUtil::parse_parameters(file_get_contents(self::$POST_INPUT));
$parameters = array_merge($parameters, $post_data);
}
// We have a Authorization-header with OAuth data. Parse the header
// and add those overriding any duplicates from GET or POST
if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
$header_parameters = OAuthUtil::split_header($request_headers['Authorization']);
$parameters = array_merge($parameters, $header_parameters);
}
}
return new OAuthRequest($http_method, $http_url, $parameters);
}
/**
* pretty much a helper function to set up the request
*/
public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters = null) {
$parameters = ($parameters) ? $parameters : array();
$defaults = array('oauth_version' => OAuthRequest::$version,
'oauth_nonce' => OAuthRequest::generate_nonce(),
'oauth_timestamp' => OAuthRequest::generate_timestamp(),
'oauth_consumer_key' => $consumer->key);
if ($token)
$defaults['oauth_token'] = $token->key;
$parameters = array_merge($defaults, $parameters);
return new OAuthRequest($http_method, $http_url, $parameters);
}
public function set_parameter($name, $value, $allow_duplicates = true) {
if ($allow_duplicates && isset($this->parameters[$name])) {
// We have already added parameter(s) with this name, so add to the list
if (is_scalar($this->parameters[$name])) {
// This is the first duplicate, so transform scalar (string)
// into an array so we can add the duplicates
$this->parameters[$name] = array($this->parameters[$name]);
}
$this->parameters[$name][] = $value;
} else {
$this->parameters[$name] = $value;
}
}
public function get_parameter($name) {
return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
}
public function get_parameters() {
return $this->parameters;
}
public function unset_parameter($name) {
unset($this->parameters[$name]);
}
/**
* The request parameters, sorted and concatenated into a normalized string.
* @return string
*/
public function get_signable_parameters() {
// Grab all parameters
$params = $this->parameters;
// Remove oauth_signature if present
// Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
if (isset($params['oauth_signature'])) {
unset($params['oauth_signature']);
}
return OAuthUtil::build_http_query($params);
}
/**
* Returns the base string of this request
*
* The base string defined as the method, the url
* and the parameters (normalized), each urlencoded
* and the concated with &.
*/
public function get_signature_base_string() {
$parts = array(
$this->get_normalized_http_method(),
$this->get_normalized_http_url(),
$this->get_signable_parameters()
);
$parts = OAuthUtil::urlencode_rfc3986($parts);
return implode('&', $parts);
}
/**
* just uppercases the http method
*/
public function get_normalized_http_method() {
return strtoupper($this->http_method);
}
/**
* parses the url and rebuilds it to be
* scheme://host/path
*/
public function get_normalized_http_url() {
$parts = parse_url($this->http_url);
$scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
$port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
$host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
$path = (isset($parts['path'])) ? $parts['path'] : '';
if (($scheme == 'https' && $port != '443')
|| ($scheme == 'http' && $port != '80')) {
$host = "$host:$port";
}
return "$scheme://$host$path";
}
/**
* builds a url usable for a GET request
*/
public function to_url() {
$post_data = $this->to_postdata();
$out = $this->get_normalized_http_url();
if ($post_data) {
$out .= '?'.$post_data;
}
return $out;
}
/**
* builds the data one would send in a POST request
*/
public function to_postdata() {
return OAuthUtil::build_http_query($this->parameters);
}
/**
* builds the Authorization: header
*/
public function to_header($realm = null) {
$first = true;
if($realm) {
$out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
$first = false;
} else
$out = 'Authorization: OAuth';
$total = array();
foreach ($this->parameters as $k => $v) {
if (substr($k, 0, 5) != "oauth") continue;
if (is_array($v)) {
throw new OAuthException('Arrays not supported in headers');
}
$out .= ($first) ? ' ' : ',';
$out .= OAuthUtil::urlencode_rfc3986($k) .
'="' .
OAuthUtil::urlencode_rfc3986($v) .
'"';
$first = false;
}
return $out;
}
public function __toString() {
return $this->to_url();
}
public function sign_request($signature_method, $consumer, $token) {
$this->set_parameter(
"oauth_signature_method",
$signature_method->get_name(),
false
);
$signature = $this->build_signature($signature_method, $consumer, $token);
$this->set_parameter("oauth_signature", $signature, false);
}
public function build_signature($signature_method, $consumer, $token) {
$signature = $signature_method->build_signature($this, $consumer, $token);
return $signature;
}
/**
* util function: current timestamp
*/
private static function generate_timestamp() {
return time();
}
/**
* util function: current nonce
*/
private static function generate_nonce() {
$mt = microtime();
$rand = mt_rand();
return md5($mt . $rand); // md5s look nicer than numbers
}
}

View File

@ -0,0 +1,233 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Server
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthServer {
protected $timestamp_threshold = 300; // in seconds, five minutes
protected $version = '1.0'; // hi blaine
protected $signature_methods = array();
protected $data_store;
function __construct($data_store) {
$this->data_store = $data_store;
}
public function add_signature_method($signature_method) {
$this->signature_methods[$signature_method->get_name()] = $signature_method;
}
// high level functions
/**
* process a request_token request
* returns the request token on success
*/
public function fetch_request_token(&$request) {
$this->get_version($request);
$consumer = $this->get_consumer($request);
// no token required for the initial token request
$token = NULL;
$this->check_signature($request, $consumer, $token);
// Rev A change
$callback = $request->get_parameter('oauth_callback');
$new_token = $this->data_store->new_request_token($consumer, $callback);
return $new_token;
}
/**
* process an access_token request
* returns the access token on success
*/
public function fetch_access_token(&$request) {
$this->get_version($request);
$consumer = $this->get_consumer($request);
// requires authorized request token
$token = $this->get_token($request, $consumer, "request");
$this->check_signature($request, $consumer, $token);
// Rev A change
$verifier = $request->get_parameter('oauth_verifier');
$new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
return $new_token;
}
/**
* verify an api call, checks all the parameters
*/
public function verify_request(&$request) {
$this->get_version($request);
$consumer = $this->get_consumer($request);
$token = $this->get_token($request, $consumer, "access");
$this->check_signature($request, $consumer, $token);
return array($consumer, $token);
}
// Internals from here
/**
* version 1
*/
private function get_version(&$request) {
$version = $request->get_parameter("oauth_version");
if (!$version) {
// Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
// Chapter 7.0 ("Accessing Protected Ressources")
$version = '1.0';
}
if ($version !== $this->version) {
throw new OAuthException("OAuth version '$version' not supported");
}
return $version;
}
/**
* figure out the signature with some defaults
*/
private function get_signature_method($request) {
$signature_method = $request instanceof OAuthRequest
? $request->get_parameter('oauth_signature_method') : NULL;
if (!$signature_method) {
// According to chapter 7 ("Accessing Protected Ressources") the signature-method
// parameter is required, and we can't just fallback to PLAINTEXT
throw new OAuthException('No signature method parameter. This parameter is required');
}
if (!in_array($signature_method,
array_keys($this->signature_methods))) {
throw new OAuthException(
"Signature method '$signature_method' not supported " .
'try one of the following: ' .
implode(', ', array_keys($this->signature_methods))
);
}
return $this->signature_methods[$signature_method];
}
/**
* try to find the consumer for the provided request's consumer key
*/
private function get_consumer($request) {
$consumer_key = $request instanceof OAuthRequest
? $request->get_parameter('oauth_consumer_key') : NULL;
if (!$consumer_key) {
throw new OAuthException('Invalid consumer key');
}
$consumer = $this->data_store->lookup_consumer($consumer_key);
if (!$consumer) {
throw new OAuthException('Invalid consumer');
}
return $consumer;
}
/**
* try to find the token for the provided request's token key
*/
private function get_token($request, $consumer, $token_type="access") {
$token_field = $request instanceof OAuthRequest
? $request->get_parameter('oauth_token') : NULL;
$token = $this->data_store->lookup_token($consumer, $token_type, $token_field);
if (!$token) {
throw new OAuthException("Invalid $token_type token: $token_field");
}
return $token;
}
/**
* all-in-one function to check the signature on a request
* should guess the signature method appropriately
*/
private function check_signature($request, $consumer, $token) {
// this should probably be in a different method
$timestamp = $request instanceof OAuthRequest
? $request->get_parameter('oauth_timestamp')
: NULL;
$nonce = $request instanceof OAuthRequest
? $request->get_parameter('oauth_nonce')
: NULL;
$this->check_timestamp($timestamp);
$this->check_nonce($consumer, $token, $nonce, $timestamp);
$signature_method = $this->get_signature_method($request);
$signature = $request->get_parameter('oauth_signature');
$valid_sig = $signature_method->check_signature($request, $consumer, $token, $signature);
if (!$valid_sig) {
throw new OAuthException('Invalid signature');
}
}
/**
* check that the timestamp is new enough
*/
private function check_timestamp($timestamp) {
if(!$timestamp)
throw new OAuthException('Missing timestamp parameter. The parameter is required');
// verify that timestamp is recentish
$now = time();
if (abs($now - $timestamp) > $this->timestamp_threshold) {
throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
}
}
/**
* check that the nonce is not repeated
*/
private function check_nonce($consumer, $token, $nonce, $timestamp) {
if(!$nonce)
throw new OAuthException('Missing nonce parameter. The parameter is required');
// verify that the nonce is uniqueish
$found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
if ($found) {
throw new OAuthException("Nonce already used: $nonce");
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,42 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to represent an %OAuth Token
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthToken {
// access tokens and request tokens
public $key;
public $secret;
/**
* key = the token
* secret = the token secret
*/
function __construct($key, $secret) {
$this->key = $key;
$this->secret = $secret;
}
/**
* generates the basic string serialization of a token that a server
* would respond to request_token and access_token calls with
*/
function to_string() {
return 'oauth_token=' .
OAuthUtil::urlencode_rfc3986($this->key) .
'&oauth_token_secret=' .
OAuthUtil::urlencode_rfc3986($this->secret);
}
function __toString() {
return $this->to_string();
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace IMSGlobal\LTI\OAuth;
/**
* Class to provide %OAuth utility methods
*
* @copyright Andy Smith
* @version 2008-08-04
* @license https://opensource.org/licenses/MIT The MIT License
*/
class OAuthUtil {
public static function urlencode_rfc3986($input) {
if (is_array($input)) {
return array_map(array('IMSGlobal\LTI\OAuth\OAuthUtil', 'urlencode_rfc3986'), $input);
} else if (is_scalar($input)) {
return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($input)));
} else {
return '';
}
}
// This decode function isn't taking into consideration the above
// modifications to the encoding process. However, this method doesn't
// seem to be used anywhere so leaving it as is.
public static function urldecode_rfc3986($string) {
return urldecode($string);
}
// Utility function for turning the Authorization: header into
// parameters, has to do some unescaping
// Can filter out any non-oauth parameters if needed (default behaviour)
// May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
// see http://code.google.com/p/oauth/issues/detail?id=163
public static function split_header($header, $only_allow_oauth_parameters = true) {
$params = array();
if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
foreach ($matches[1] as $i => $h) {
$params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
}
if (isset($params['realm'])) {
unset($params['realm']);
}
}
return $params;
}
// helper to try to sort out headers for people who aren't running apache
public static function get_headers() {
if (function_exists('apache_request_headers')) {
// we need this to get the actual Authorization: header
// because apache tends to tell us it doesn't exist
$headers = apache_request_headers();
// sanitize the output of apache_request_headers because
// we always want the keys to be Cased-Like-This and arh()
// returns the headers in the same case as they are in the
// request
$out = array();
foreach ($headers AS $key => $value) {
$key = str_replace(" ", "-", ucwords(strtolower(str_replace("-", " ", $key))));
$out[$key] = $value;
}
} else {
// otherwise we don't have apache and are just going to have to hope
// that $_SERVER actually contains what we need
$out = array();
if( isset($_SERVER['CONTENT_TYPE']) )
$out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
if( isset($_ENV['CONTENT_TYPE']) )
$out['Content-Type'] = $_ENV['CONTENT_TYPE'];
foreach ($_SERVER as $key => $value) {
if (substr($key, 0, 5) == 'HTTP_') {
// this is chaos, basically it is just there to capitalize the first
// letter of every word that is not an initial HTTP and strip HTTP
// code from przemek
$key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
$out[$key] = $value;
}
}
}
return $out;
}
// This function takes a input like a=b&a=c&d=e and returns the parsed
// parameters like this
// array('a' => array('b','c'), 'd' => 'e')
public static function parse_parameters( $input ) {
if (!isset($input) || !$input) return array();
$pairs = explode('&', $input);
$parsed_parameters = array();
foreach ($pairs as $pair) {
$split = explode('=', $pair, 2);
$parameter = self::urldecode_rfc3986($split[0]);
$value = isset($split[1]) ? self::urldecode_rfc3986($split[1]) : '';
if (isset($parsed_parameters[$parameter])) {
// We have already recieved parameter(s) with this name, so add to the list
// of parameters with this name
if (is_scalar($parsed_parameters[$parameter])) {
// This is the first duplicate, so transform scalar (string) into an array
// so we can add the duplicates
$parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
}
$parsed_parameters[$parameter][] = $value;
} else {
$parsed_parameters[$parameter] = $value;
}
}
return $parsed_parameters;
}
public static function build_http_query($params) {
if (!$params) return '';
// Urlencode both keys and values
$keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
$values = OAuthUtil::urlencode_rfc3986(array_values($params));
$params = array_combine($keys, $values);
// Parameters are sorted by name, using lexicographical byte value ordering.
// Ref: Spec: 9.1.1 (1)
uksort($params, 'strcmp');
$pairs = array();
foreach ($params as $parameter => $value) {
if (is_array($value)) {
// If two or more parameters share the same name, they are sorted by their value
// Ref: Spec: 9.1.1 (1)
// June 12th, 2010 - changed to sort because of issue 164 by hidetaka
sort($value, SORT_STRING);
foreach ($value as $duplicate_value) {
$pairs[] = $parameter . '=' . $duplicate_value;
}
} else {
$pairs[] = $parameter . '=' . $value;
}
}
// For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
// Each name-value pair is separated by an '&' character (ASCII code 38)
return implode('&', $pairs);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace IMSGlobal\LTI\Profile;
/**
* Class to represent a generic item object
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.0
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
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;
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,105 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
/**
* Class to represent a tool consumer nonce
*
* @author Stephen P Vickers <svickers@imsglobal.org>
* @copyright IMS Global Learning Consortium Inc
* @date 2016
* @version 3.0.2
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
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 int $expires
*/
public $expires = null;
/**
* Tool Consumer to which this nonce applies.
*
* @var ToolConsumer $consumer
*/
private $consumer = null;
/**
* Nonce value.
*
* @var string $value
*/
private $value = null;
/**
* Class constructor.
*
* @param ToolConsumer $consumer Consumer object
* @param string $value Nonce value (optional, default is null)
*/
public function __construct($consumer, $value = null)
{
$this->consumer = $consumer;
$this->value = $value;
$this->expires = time() + (self::MAX_NONCE_AGE * 60);
}
/**
* Load a nonce value from the database.
*
* @return boolean True if the nonce value was successfully loaded
*/
public function load()
{
return $this->consumer->getDataConnector()->loadConsumerNonce($this);
}
/**
* Save a nonce value in the database.
*
* @return boolean True if the nonce value was successfully saved
*/
public function save()
{
return $this->consumer->getDataConnector()->saveConsumerNonce($this);
}
/**
* Get tool consumer.
*
* @return ToolConsumer Consumer for this nonce
*/
public function getConsumer()
{
return $this->consumer;
}
/**
* Get outcome value.
*
* @return string Outcome value
*/
public function getValue()
{
return $this->value;
}
}

View File

@ -0,0 +1,138 @@
<?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.2
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
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
* @return string
*/
public static function toJson($items)
{
/*
$data = array();
if (!is_array($items)) {
$data[] = json_encode($items);
} else {
foreach ($items as $item) {
$data[] = json_encode($item);
}
}
$json = '{ "@context" : "http://purl.imsglobal.org/ctx/lti/v1/ContentItem", "@graph" : [' . implode(", ", $data) . '] }';
*/
$obj = new \stdClass();
$obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem';
if (!is_array($items)) {
$obj->{'@graph'} = array();
$obj->{'@graph'}[] = $items;
} else {
$obj->{'@graph'} = $items;
}
return json_encode($obj);
}
}

View File

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

View File

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

View File

@ -0,0 +1,460 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\DataConnector\DataConnector;
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.2
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
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 int $created
*/
public $created = null;
/**
* Date/time when the object was last updated.
*
* @var int $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 context ID.
*
* @return string ID for this context.
*/
public function getId()
{
return $this->ltiContextId;
}
/**
* 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 ToolConsumer $consumer Consumer instance
* @param string $ltiContextId LTI Context ID value
* @return Context
*/
public static function fromConsumer($consumer, $ltiContextId)
{
$context = new Context();
$context->consumer = $consumer;
$context->dataConnector = $consumer->getDataConnector();
$context->ltiContextId = $ltiContextId;
if (!empty($ltiContextId)) {
$context->load();
}
return $context;
}
###
### PRIVATE METHODS
###
/**
* Load the context from the database.
*
* @param int $id Record ID of context (optional, default is null)
*
* @return boolean True if context was successfully loaded
*/
private function load($id = null)
{
$this->initialize();
$this->id = $id;
return $this->getDataConnector()->loadContext($this);
}
}

View File

@ -0,0 +1,607 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\DataConnector;
use IMSGlobal\LTI\ToolProvider\ConsumerNonce;
use IMSGlobal\LTI\ToolProvider\Context;
use IMSGlobal\LTI\ToolProvider\ResourceLink;
use IMSGlobal\LTI\ToolProvider\ResourceLinkShareKey;
use IMSGlobal\LTI\ToolProvider\ToolConsumer;
use IMSGlobal\LTI\ToolProvider\ToolProxy;
use IMSGlobal\LTI\ToolProvider\User;
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.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
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 bool $addQuotes If true the returned string will be enclosed in single quotes (optional, default is true)
* @return string The quoted string.
*/
static function quoted($value, $addQuotes = true)
{
if (is_null($value)) {
$value = 'null';
} else {
$value = str_replace('\'', '\'\'', $value);
if ($addQuotes) {
$value = "'{$value}'";
}
}
return $value;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,199 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\DataConnector;
use IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\Context;
use IMSGlobal\LTI\ToolProvider\ToolConsumer;
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.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
class DataConnector_pdo_sqlite extends DataConnector_pdo
{
###
### ToolConsumer methods
###
/**
* Delete tool consumer object.
*
* @param ToolConsumer $consumer Consumer object
*
* @return boolean True if the tool consumer object was successfully deleted
*/
public function deleteToolConsumer($consumer)
{
$id = $consumer->getRecordId();
// Delete any nonce values for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE consumer_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any outstanding share keys for resource links for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any outstanding share keys for resource links for contexts in this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any users in resource links for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any users in resource links for contexts in this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Update any resource links for which this consumer is acting as a primary resource link
$sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'SET primary_resource_link_pk = NULL, share_approved = NULL ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Update any resource links for contexts in which this consumer is acting as a primary resource link
$sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'SET primary_resource_link_pk = NULL, share_approved = NULL ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any resource links for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'WHERE consumer_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any resource links for contexts in this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.context_pk = c.context_pk) AND (c.consumer_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any contexts for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' .
'WHERE consumer_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' .
'WHERE consumer_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$ok = $query->execute();
if ($ok) {
$consumer->initialize();
}
return $ok;
}
###
### Context methods
###
/**
* Delete context object.
*
* @param Context $context Context object
*
* @return boolean True if the Context object was successfully deleted
*/
public function deleteContext($context)
{
$id = $context->getRecordId();
// Delete any outstanding share keys for resource links for this context
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any users in resource links for this context
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Update any resource links for which this consumer is acting as a primary resource link
$sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'SET primary_resource_link_pk = null, share_approved = null ' .
"WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' .
"WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete any resource links for this consumer
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' .
'WHERE context_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$query->execute();
// Delete context
$sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' .
'WHERE context_pk = :id';
$query = $this->db->prepare($sql);
$query->bindValue('id', $id, PDO::PARAM_INT);
$ok = $query->execute();
if ($ok) {
$context->initialize();
}
return $ok;
}
}

View File

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

View File

@ -0,0 +1,52 @@
<?php
namespace IMSGlobal\LTI\ToolProvider\MediaType;
use IMSGlobal\LTI\ToolProvider\ToolProvider;
use IMSGlobal\LTI\Profile\ResourceHandler as ProfileResourceHandler;
/**
* 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 ProfileResourceHandler $resourceHandler Resource handler object
*/
function __construct($toolProvider, $resourceHandler)
{
$this->resource_type = new \stdClass;
$this->resource_type->code = $resourceHandler->item->id;
$this->resource_name = new \stdClass;
$this->resource_name->default_value = $resourceHandler->item->name;
$this->resource_name->key = "{$resourceHandler->item->id}.resource.name";
$this->description = new \stdClass;
$this->description->default_value = $resourceHandler->item->description;
$this->description->key = "{$resourceHandler->item->id}.resource.description";
$this->icon_info = new \stdClass;
$this->icon_info->default_location = new \stdClass;
$this->icon_info->default_location->path = $resourceHandler->icon;
$this->icon_info->key = "{$resourceHandler->item->id}.icon.path";
$this->message = array();
foreach ($resourceHandler->requiredMessages as $message) {
$this->message[] = new Message($message, $toolProvider->consumer->profile->capability_offered);
}
foreach ($resourceHandler->optionalMessages as $message) {
if (in_array($message->type, $toolProvider->consumer->profile->capability_offered)) {
$this->message[] = new Message($message, $toolProvider->consumer->profile->capability_offered);
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,196 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\DataConnector\DataConnector;
/**
* 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.2
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
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 int $expires
*/
public $expires = null;
/**
* Share key value.
*
* @var string $id
*/
private $id = null;
/**
* Data connector.
*
* @var DataConnector $dataConnector
*/
private $dataConnector = null;
/**
* Class constructor.
*
* @param ResourceLink $resourceLink Resource_Link object
* @param string $id Value of share key (optional, default is null)
*/
public function __construct($resourceLink, $id = null)
{
$this->initialize();
$this->dataConnector = $resourceLink->getDataConnector();
$this->resourceLinkId = $resourceLink->getRecordId();
$this->id = $id;
if (!empty($id)) {
$this->load();
}
}
/**
* Initialise the resource link share key.
*/
public function initialize()
{
$this->length = null;
$this->life = null;
$this->autoApprove = false;
$this->expires = null;
}
/**
* Initialise the resource link share key.
*
* Pseudonym for initialize().
*/
public function initialise()
{
$this->initialize();
}
/**
* Save the resource link share key to the database.
*
* @return boolean True if the share key was successfully saved
*/
public function save()
{
if (empty($this->life)) {
$this->life = self::DEFAULT_SHARE_KEY_LIFE;
} else {
$this->life = max(min($this->life, self::MAX_SHARE_KEY_LIFE), 0);
}
$this->expires = time() + ($this->life * 60 * 60);
if (empty($this->id)) {
if (empty($this->length) || !is_numeric($this->length)) {
$this->length = self::MAX_SHARE_KEY_LENGTH;
} else {
$this->length = max(min($this->length, self::MAX_SHARE_KEY_LENGTH), self::MIN_SHARE_KEY_LENGTH);
}
$this->id = DataConnector::getRandomString($this->length);
}
return $this->dataConnector->saveResourceLinkShareKey($this);
}
/**
* Delete the resource link share key from the database.
*
* @return boolean True if the share key was successfully deleted
*/
public function delete()
{
return $this->dataConnector->deleteResourceLinkShareKey($this);
}
/**
* Get share key value.
*
* @return string Share key value
*/
public function getId()
{
return $this->id;
}
###
### PRIVATE METHOD
###
/**
* Load the resource link share key from the database.
*/
private function load()
{
$this->initialize();
$this->dataConnector->loadResourceLinkShareKey($this);
if (!is_null($this->id)) {
$this->length = strlen($this->id);
}
if (!is_null($this->expires)) {
$this->life = ($this->expires - time()) / 60 / 60;
}
}
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,194 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\DataConnector\DataConnector;
use IMSGlobal\LTI\ToolProvider\MediaType;
/**
* 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.2
* @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
*/
private $dataConnector = null;
/**
* Tool Proxy document.
*
* @var MediaType\ToolProxy $toolProxy
*/
private $toolProxy = null;
/**
* Class constructor.
*
* @param 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::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;
}
}

View File

@ -0,0 +1,473 @@
<?php
namespace IMSGlobal\LTI\ToolProvider;
use IMSGlobal\LTI\ToolProvider\DataConnector\DataConnector;
/**
* 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.2
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
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 = '';
/**
* User's image URI.
*
* @var string $image
*/
public $image = '';
/**
* 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->image = '';
$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 ResourceLink 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
* @return User
*/
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)) {
return $dataConnector->loadUser($this);
}
return false;
}
}

View File

@ -1060,4 +1060,48 @@ EOD;
$gradeoutcome->update_from_db();
return $gradeoutcome->get_record_data();
}
/**
* Helper function used to create an LTI tool.
*
* @param array $data
* @return stdClass the tool
*/
public function create_lti_tool($data = array()) {
global $DB;
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
// Create a course if no course id was specified.
if (empty($data->courseid)) {
$course = $this->create_course();
$data->courseid = $course->id;
} else {
$course = get_course($data->courseid);
}
if (!empty($data->cmid)) {
$data->contextid = context_module::instance($data->cmid)->id;
} else {
$data->contextid = context_course::instance($data->courseid)->id;
}
// Set it to enabled if no status was specified.
if (!isset($data->status)) {
$data->status = ENROL_INSTANCE_ENABLED;
}
// Add some extra necessary fields to the data.
$data->name = 'Test LTI';
$data->roleinstructor = $studentrole->id;
$data->rolelearner = $teacherrole->id;
// Get the enrol LTI plugin.
$enrolplugin = enrol_get_plugin('lti');
$instanceid = $enrolplugin->add_instance($course, (array) $data);
// Get the tool associated with this instance.
return $DB->get_record('enrol_lti_tools', array('enrolid' => $instanceid));
}
}

View File

@ -291,4 +291,11 @@
<license>Apache 2.0</license>
<version>1.1.0</version>
</library>
<library>
<location>ltiprovider</location>
<name>LTI Tool Provider Library PHP</name>
<license>Apache</license>
<version>3.0.2</version>
<licenseversion>2.0</licenseversion>
</library>
</libraries>