When I try to POST this XML document to Blackboard Learn LIS using PHP cURL I get this error. I'm checking the XML with simplexml to ensure it is well formed, so that's not the issue. I have also checked the XML document to ensure there is no BOM data attached. I've opened it in a Hex editor to ensure this.
I'm completely stumped on this one and I don't have access to the Blackboard logs.
PHP:
include("xml/envelope.php");
//sanity check to see if xml is well formed
$sxml = simplexml_load_string($xml);
$xml = $sxml->asXML();
$xml_length = strlen($xml);
$url = $this->lis_outcome_service_url;
$bodyHash = base64_encode(sha1($xml, TRUE)); // build oauth_body_hash
$consumer = new OAuthConsumer($key, $secret);
$request = OAuthRequest::from_consumer_and_token($consumer, '', 'POST', $url, array('oauth_body_hash' => $bodyHash) );
$request->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), $consumer, '');
$header = $request->to_header() . "Content-Type: application/xml\r\nContent-Length: $xml_length\r\n"; // add content type header
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array($header));
//curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POST, 1);
//curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
//curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$output .= "$url<br><br>$output<pre>$header\n\n".htmlspecialchars($xml)."</pre><p>ERRORS?: ".curl_error($ch);
curl_close($ch);// "<p>Saved grade: "+var_export($output)+"</p>";
return $output;
The XML in envelope.php:
<?php
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<imsx_POXHeader>
<imsx_POXRequestHeaderInfo>
<imsx_version>V1.0</imsx_version>
<imsx_messageIdentifier>$id</imsx_messageIdentifier>
</imsx_POXRequestHeaderInfo>
</imsx_POXHeader>
<imsx_POXBody>
<replaceResultRequest>
<resultRecord>
<sourcedGUID>
<sourcedId>$this->lis_result_sourcedid</sourcedId>
</sourcedGUID>
<result>
<resultScore>
<language>en-us</language>
<textString>0.7</textString>
</resultScore>
</result>
</resultRecord>
</replaceResultRequest>
</imsx_POXBody>
</imsx_POXEnvelopeRequest>
XML;
?>
The XML output in browser after parsing (during cURL request):
<?xml version="1.0" encoding="UTF-8"?>
<imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<imsx_POXHeader>
<imsx_POXRequestHeaderInfo>
<imsx_version>V1.0</imsx_version>
<imsx_messageIdentifier>53e4657ec1499</imsx_messageIdentifier>
</imsx_POXRequestHeaderInfo>
</imsx_POXHeader>
<imsx_POXBody>
<replaceResultRequest>
<resultRecord>
<sourcedGUID>
<sourcedId>bbgc15659375gi290156</sourcedId>
</sourcedGUID>
<result>
<resultScore>
<language>en-us</language>
<textString>0.7</textString>
</resultScore>
</result>
</resultRecord>
</replaceResultRequest>
</imsx_POXBody>
</imsx_POXEnvelopeRequest>
Since there is so little detailed information on how to do this in LTI, I've posted my solution below. This is a duplicate of my addition to Blackboard's EduGarage forums, but to get it into the public domain I've put it here. I hope it helps other people trying to implement LTI tools.
Here is how to pass back a grade using PHP cURL, the contents of the envelope.php file should
follow the XML found in the IMS Global Documentation found here. The cURL request constructs the header and then adds the XML body below it.
Note that the envelope.php file contains the $xml variable in the following format (you will obviously need to add your own $id and set a grade with a variable etc...):
PHP code for passing grade using cURL:
include("xml/envelope.php"); $xml_length = strlen($xml); $url = $this->lis_outcome_service_url; $bodyHash = base64_encode(sha1($xml, TRUE)); // build oauth_body_hash $consumer = new OAuthConsumer($key, $secret); $request = OAuthRequest::from_consumer_and_token($consumer, '', 'POST', $url, array('oauth_body_hash' => $bodyHash) ); $request->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), $consumer, ''); $header .= $request->to_header(); // add content type header $ch = curl_init($url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HTTPHEADER, array("POST http://your-tools-url-here.edu.au HTTP/1.0", "Content-Length: $xml_length", $header, "Content-Type: application/xml")); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); curl_close($ch);