moodle/mod/lti/service.php

392 lines
14 KiB
PHP
Raw Normal View History

2011-08-25 12:38:12 -04:00
<?php
// This file is part of BasicLTI4Moodle
//
// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
// are already supporting or going to support BasicLTI. This project Implements the consumer
// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
// at the GESSI research group at UPC.
// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
//
// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
// of the Universitat Politecnica de Catalunya http://www.upc.edu
// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu
//
// 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 contains all necessary code to support basiclti services
* like outcomes and roster access.
*
2011-08-31 10:13:20 -04:00
* @package lti
2011-08-25 12:38:12 -04:00
* @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
* marc.alier@upc.edu
* @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu
*
* @author Marc Alier
* @author Jordi Piguillem
* @author Nikolas Galanis
* @author Charles Severance
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php");
2011-08-31 10:13:20 -04:00
require_once($CFG->dirroot.'/mod/lti/lib.php');
require_once($CFG->dirroot.'/mod/lti/locallib.php');
require_once($CFG->dirroot.'/mod/lti/OAuth.php');
require_once($CFG->dirroot.'/mod/lti/TrivialStore.php');
2011-08-25 12:38:12 -04:00
error_reporting(E_ALL & ~E_NOTICE);
ini_set("display_errors", 1);
$PAGE->set_context(get_context_instance(CONTEXT_SYSTEM));
2011-08-31 10:13:20 -04:00
$PAGE->set_url('/mod/lti/service.php');
2011-08-25 12:38:12 -04:00
$PAGE->set_pagetype('admin-setting-' . $section);
$PAGE->set_pagelayout('admin');
$PAGE->navigation->clear_cache();
function message_response($major, $severity, $minor=false, $message=false, $xml=false) {
$lti_message_type = $_REQUEST['lti_message_type'];
$retval = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"."\n" .
"<message_response>\n" .
" <lti_message_type>$lti_message_type</lti_message_type>\n" .
" <statusinfo>\n" .
" <codemajor>$major</codemajor>\n" .
" <severity>$severity</severity>\n";
if (! $codeminor === false) {
$retval = $retval . " <codeminor>$minor</codeminor>\n";
}
$retval = $retval .
" <description>$message</description>\n" .
" </statusinfo>\n";
if (! $xml === false) {
$retval = $retval . $xml;
}
$retval = $retval . "</message_response>\n";
return $retval;
}
function do_error($message) {
print message_response('Fail', 'Error', false, $message);
exit();
}
$lti_version = $_REQUEST['lti_version'];
if ($lti_version != "LTI-1p0") {
do_error("Improperly formed message: wrong lti version: ".$lti_version);
}
$lti_message_type = $_REQUEST['lti_message_type'];
if (! isset($lti_message_type)) {
do_error("Improperly formed message: no lti_message_type parameter");
}
$message_type = false;
if ($lti_message_type == "basic-lis-replaceresult" ||
$lti_message_type == "basic-lis-createresult" ||
$lti_message_type == "basic-lis-updateresult" ||
$lti_message_type == "basic-lis-deleteresult" ||
$lti_message_type == "basic-lis-readresult") {
$sourcedid = $_REQUEST['sourcedid'];
$message_type = "basicoutcome";
} else if ($lti_message_type == "basic-lti-loadsetting" ||
$lti_message_type == "basic-lti-savesetting" ||
$lti_message_type == "basic-lti-deletesetting") {
$sourcedid = $_REQUEST['id'];
$message_type = "toolsetting";
} else if ($lti_message_type == "basic-lis-readmembershipsforcontext") {
$sourcedid = $_REQUEST['id'];
$message_type = "roster";
}
if ($message_type == false) {
do_error("Illegal lti_message_type");
}
if (!isset($sourcedid)) {
do_error("sourcedid missing");
}
// Truncate to maximum length
$sourcedid = substr($sourcedid, 0, 2048);
try {
$info = explode(':::', $sourcedid);
if (! is_array($info)) {
do_error("Bad sourcedid (1)");
}
$signature = $info[0];
$userid = intval($info[1]);
$placement = $info[2];
} catch (Exception $e) {
do_error("Bad sourcedid (2)");
}
if (isset($signature) && isset($userid) && isset($placement)) {
// OK
} else {
do_error("Bad sourcedid (3)");
}
// Retrieve the Basic LTI placement
2011-08-31 10:13:20 -04:00
if (! $basiclti = $DB->get_record('lti', array('id'=>$placement))) {
2011-08-25 12:38:12 -04:00
do_error("Bad sourcedid (4)");
}
$basiclti_types_config = (object)$basiclti_types_config;
2011-08-31 10:13:20 -04:00
$typeconfig = lti_get_type_config($basiclti->typeid);
2011-08-25 12:38:12 -04:00
if (isset($typeconfig) && isset($typeconfig['password'])) {
// OK
} else {
do_error("Unable to load type");
}
if ($message_type == "basicoutcome") {
if ($typeconfig["acceptgrades"] == 1 ||
($typeconfig["acceptgrades"] == 2 && $basiclti->instructorchoiceacceptgrades == 1)) {
// The placement is configured to accept grades
} else {
do_error("Not permitted (1)");
}
} else if ($message_type == "toolsetting") {
if ($typeconfig["allowsetting"] == 1 ||
($typeconfig["allowsetting"] == 2 && $basiclti->instructorchoiceallowsetting == 1)) {
// OK
} else {
do_error("Not permitted (2)");
}
} else if ($message_type == "roster") {
if ($typeconfig["allowroster"] == 1 ||
($typeconfig["allowroster"] == 2 && $basiclti->instructorchoiceallowroster == 1)) {
// OK
} else {
do_error("Not permitted (3)");
}
}
// Retrieve the secret we use to sign lis_result_sourcedid
$placementsecret = $basiclti->placementsecret;
$oldplacementsecret = $basiclti->oldplacementsecret;
if (! isset($placementsecret)) {
do_error("Not permitted (4)");
}
$suffix = ':::' . $userid . ':::' . $placement;
$plaintext = $placementsecret . $suffix;
$hashsig = hash('sha256', $plaintext, false);
if (($hashsig != $signature) && isset($oldplacementsecret) && (strlen($oldplacementsecret) > 1)) {
$plaintext = $oldplacementsecret . $suffix;
$hashsig = hash('sha256', $plaintext, false);
}
if ($hashsig != $signature) {
do_error("Invalid sourcedid");
}
// Check the OAuth Signature
$oauth_secret = $typeconfig["password"];
$oauth_consumer_key = $typeconfig["resourcekey"];
if (! isset($oauth_secret)) {
do_error("Not permitted (5)");
}
if (! isset($oauth_consumer_key)) {
do_error("Not permitted (6)");
}
// Verify the message signature
$store = new TrivialOAuthDataStore();
$store->add_consumer($oauth_consumer_key, $oauth_secret);
$server = new OAuthServer($store);
$method = new OAuthSignatureMethod_HMAC_SHA1();
$server->add_signature_method($method);
$request = OAuthRequest::from_request();
$basestring = $request->get_signature_base_string();
try {
$server->verify_request($request);
} catch (Exception $e) {
do_error($e->getMessage());
}
if (! $course = $DB->get_record('course', array('id'=>$basiclti->course))) {
do_error("Could not retrieve course");
}
// TODO: Check that user is in course
2011-08-31 10:13:20 -04:00
if (! $cm = get_coursemodule_from_instance("lti", $basiclti->id, $course->id)) {
2011-08-25 12:38:12 -04:00
do_error("Course Module ID was incorrect");
}
// Lets store the grade
require_once($CFG->libdir.'/gradelib.php');
// Beginning of actual grade processing
if ($message_type == "basicoutcome") {
2011-08-31 10:13:20 -04:00
$source = 'mod/lti';
2011-08-25 12:38:12 -04:00
$courseid = $course->id;
$itemtype = 'mod';
2011-08-31 10:13:20 -04:00
$itemmodule = 'lti';
2011-08-25 12:38:12 -04:00
$iteminstance = $basiclti->id;
if ($lti_message_type == "basic-lis-readresult") {
unset($grade);
$thegrade = grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid);
// print_r($thegrade->items[0]->grades);
if (isset($thegrade) && is_array($thegrade->items[0]->grades)) {
foreach ($thegrade->items[0]->grades as $agrade) {
$grade = $agrade->grade;
break;
}
}
if (! isset($grade)) {
do_error("Unable to read grade");
}
$result = " <result>\n" .
" <resultscore>\n" .
" <textstring>" .
htmlspecialchars($grade/100.0) .
"</textstring>\n" .
" </resultscore>\n" .
" </result>\n";
print message_response('Success', 'Status', false, "Grade read", $result);
exit();
}
if ($lti_message_type == "basic-lis-deleteresult") {
$params = array();
$params['itemname'] = $basiclti->name;
$grade = new stdClass();
$grade->userid = $userid;
$grade->rawgrade = null;
grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, 0, $grade, array('deleted'=>1));
} else {
if (isset($_REQUEST['result_resultscore_textstring'])) {
$gradeval = floatval($_REQUEST['result_resultscore_textstring']);
if ($gradeval <= 1.0 && $gradeval >= 0.0) {
$gradeval = $gradeval * 100.0;
}
} else {
do_error('Missing Grade');
}
$params = array();
$params['itemname'] = $basiclti->name;
$grade = new stdClass();
$grade->userid = $userid;
$grade->rawgrade = $gradeval;
grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, 0, $grade, $params);
}
print message_response('Success', 'Status', 'fullsuccess', 'Grade updated');
} else if ($lti_message_type == "basic-lti-loadsetting") {
$xml = " <setting>\n" .
" <value>".htmlspecialchars($basiclti->setting)."</value>\n" .
" </setting>\n";
print message_response('Success', 'Status', 'fullsuccess', 'Setting retrieved', $xml);
} else if ($lti_message_type == "basic-lti-savesetting") {
$setting = $_REQUEST['setting'];
if (! isset($setting)) {
do_error('Missing setting value');
}
2011-08-31 10:13:20 -04:00
$record = $DB->get_record('lti', array('id'=>$basiclti->id));
2011-08-25 12:38:12 -04:00
$record->setting = $setting;
2011-08-31 10:13:20 -04:00
$success = $DB->update_record('lti', $record);
2011-08-25 12:38:12 -04:00
if ($success) {
print message_response('Success', 'Status', 'fullsuccess', 'Setting updated');
} else {
do_error("Error updating error");
}
} else if ($lti_message_type == "basic-lti-deletesetting") {
2011-08-31 10:13:20 -04:00
$record = $DB->get_record('lti', array('id'=>$basiclti->id));
2011-08-25 12:38:12 -04:00
$record->setting = '';
2011-08-31 10:13:20 -04:00
$success = $DB->update_record('lti', $record);
2011-08-25 12:38:12 -04:00
if ($success) {
print message_response('Success', 'Status', 'fullsuccess', 'Setting deleted');
} else {
do_error("Error updating error");
}
} else if ($message_type == "roster") {
if (! $course = $DB->get_record('course', array('id'=>$basiclti->course))) {
do_error("Could not retrieve course");
}
if (! $context = get_context_instance(CONTEXT_COURSE, $course->id)) {
do_error("Could not retrieve context");
}
$sql = 'SELECT u.id, u.username, u.firstname, u.lastname, u.email, ro.shortname
FROM '.$CFG->prefix.'role_assignments ra
JOIN '.$CFG->prefix.'user AS u ON ra.userid = u.id
JOIN '.$CFG->prefix.'role ro ON ra.roleid = ro.id
WHERE ra.contextid = '.$context->id;
$userlist = $DB->get_recordset_sql($sql);
$xml = " <memberships>\n";
foreach ($userlist as $user) {
$role = "Learner";
if ($user->shortname == 'editingteacher' || $user->shortname == 'admin') {
$role = 'Instructor';
}
$userxml = " <member>\n".
" <user_id>".htmlspecialchars($user->id)."</user_id>\n".
" <roles>$role</roles>\n";
if ($typeconfig["sendname"] == 1 ||
($typeconfig["sendname"] == 2 && $basiclti->instructorchoicesendname == 1)) {
if (isset($user->firstname)) {
$userxml .= " <person_name_given>".htmlspecialchars($user->firstname)."</person_name_given>\n";
}
if (isset($user->lastname)) {
$userxml .= " <person_name_family>".htmlspecialchars($user->lastname)."</person_name_family>\n";
}
}
if ($typeconfig["sendemailaddr"] == 1 ||
($typeconfig["sendemailaddr"] == 2 && $basiclti->instructorchoicesendemailaddr == 1)) {
if (isset($user->email)) {
$userxml .= " <person_contact_email_primary>".htmlspecialchars($user->email)."</person_contact_email_primary>\n";
}
}
$placementsecret = $basiclti->placementsecret;
if (isset($placementsecret)) {
$suffix = ':::' . $user->id . ':::' . $basiclti->id;
$plaintext = $placementsecret . $suffix;
$hashsig = hash('sha256', $plaintext, false);
$sourcedid = $hashsig . $suffix;
}
if ($typeconfig["acceptgrades"] == 1 ||
($typeconfig["acceptgrades"] == 2 && $basiclti->instructorchoiceacceptgrades == 1)) {
if (isset($sourcedid)) {
$userxml .= " <lis_result_sourcedid>".htmlspecialchars($sourcedid)."</lis_result_sourcedid>\n";
}
}
$userxml .= " </member>\n";
$xml .= $userxml;
}
$xml .= " </memberships>\n";
print message_response('Success', 'Status', 'fullsuccess', 'Roster retreived', $xml);
}