move code up one directory
[atutor.git] / mods / _standard / basiclti / launch / service.php
1 <?php
2
3 define('AT_INCLUDE_PATH', '../../../include/');
4 require(AT_INCLUDE_PATH.'config.inc.php');
5 require_once(AT_INCLUDE_PATH.'lib/mysql_connect.inc.php');
6
7     require_once("ims-blti/OAuth.php");
8     require_once("TrivialStore.php");
9
10 error_reporting(E_ALL & ~E_NOTICE);
11 ini_set("display_errors", 1);
12
13     function message_response($major, $severity, $minor=false, $message=false, $xml=false) {
14         $lti_message_type = $_REQUEST['lti_message_type'];
15         $retval = '<?xml version="1.0" encoding="UTF-8"?>'."\n" .
16         "<message_response>\n" .
17         "  <lti_message_type>$lti_message_type</lti_message_type>\n" .
18         "  <statusinfo>\n" .
19         "     <codemajor>$major</codemajor>\n" .
20         "     <severity>$severity</severity>\n";
21         if ( ! $codeminor === false ) $retval = $retval .  "     <codeminor>$minor</codeminor>\n";
22         $retval = $retval . 
23         "     <description>$message</description>\n" .
24         "  </statusinfo>\n";
25         if ( ! $xml === false ) $retval = $retval . $xml;
26         $retval = $retval . "</message_response>\n";
27         return $retval;
28     }
29
30     function doError($message) {
31         print message_response('Fail', 'Error', false, $message);
32         exit();
33     }
34
35     $lti_version = $_REQUEST['lti_version'];
36     if ( $lti_version != "LTI-1p0" ) doError("Improperly formed message");
37
38     $lti_message_type = $_REQUEST['lti_message_type'];
39     if ( ! isset($lti_message_type) ) doError("Improperly formed message");
40
41     $message_type = false;
42     if( $lti_message_type == "basic-lis-replaceresult" ||
43         $lti_message_type == "basic-lis-createresult" ||
44         $lti_message_type == "basic-lis-updateresult" ||
45         $lti_message_type == "basic-lis-deleteresult" ||
46         $lti_message_type == "basic-lis-readresult" ) {
47           $sourcedid = $_REQUEST['sourcedid'];
48           $message_type = "basicoutcome";
49     } else if ( $lti_message_type == "basic-lti-loadsetting" ||
50         $lti_message_type == "basic-lti-savesetting" ||
51         $lti_message_type == "basic-lti-deletesetting" ) {
52           $sourcedid = $_REQUEST['id'];
53           $message_type = "toolsetting";
54     } else if ( $lti_message_type == "basic-lis-readmembershipsforcontext") {
55           $sourcedid = $_REQUEST['id'];
56           $message_type = "roster";
57     }
58
59     if ( $message_type == false ) {
60         doError("Illegal lti_message_type");
61     }
62
63     if ( !isset($sourcedid) ) {
64         doError("sourcedid missing");
65     }
66     // Truncate to maximum length
67     $sourcedid = substr($sourcedid, 0, 2048);
68
69     try {
70         $info = explode(':::',$sourcedid);
71         if ( ! is_array($info) ) doError("Bad sourcedid");
72         $signature = $info[0];
73         $userid = intval($info[1]);
74         $placement = $info[2];
75     }
76     catch(Exception $e) {
77         doError("Bad sourcedid");
78     }
79
80     if ( isset($signature) && isset($userid) && isset($placement) ) {
81         // OK
82     } else {
83         doError("Bad sourcedid");
84     }
85
86 function loadError($msg) {
87    doError($msg);
88 }
89
90 $content_id = $placement;
91 $member_id = $userid;
92 require("loadrows.php");
93 $course_id = $atutor_content_row['course_id'];
94 // echo("basiclti_content_row<br/>\n");print_r($basiclti_content_row); echo("<hr>\n");
95 // echo("basiclti_tool_row<br/>\n");print_r($basiclti_tool_row); echo("<hr>\n");
96 // echo("atutor_content_row<br/>\n");print_r($atutor_content_row); echo("<hr>\n");
97 // echo("atutor_course_row<br/>\n");print_r($atutor_course_row); echo("<hr>\n");
98 // These two might not be important here
99 // echo("atutor_member_row<br/>\n");print_r($atutor_member_row); echo("<hr>\n");
100 // echo("atutor_course_membership_row<br/>\n");print_r($atutor_course_membership_row); echo("<hr>\n");
101
102     if ( $message_type == "basicoutcome" ) {
103         if ( $basiclti_tool_row['acceptgrades'] == 1 && $basiclti_content_row['gradebook_test_id'] > 0 ) {
104             // The placement is configured to accept grades
105         } else { 
106             doError("Not permitted");
107         }
108     } else if ( $message_type == "roster" ) {
109         if ( $basiclti_tool_row['allowroster'] == 1 ||
110            ( $basiclti_tool_row['allowroster'] == 2 && $basiclti_content_row['allowroster'] == 1 ) ) {
111             // OK
112         } else { 
113             doError("Not permitted");
114         }
115     } else if ( $message_type == "toolsetting" ) {
116         if  ( $basiclti_tool_row['allowsetting'] == 1 ||
117             ( $basiclti_tool_row['allowsetting'] == 2 && $basiclti_content_row['allowsetting'] == 1 ) ) {
118             // OK
119         } else { 
120             doError("Not permitted");
121         }
122     }
123
124     // Retrieve the secret we use to sign lis_result_sourcedid
125     $placementsecret = $basiclti_content_row['placementsecret'];
126     $oldplacementsecret = $basiclti_content_row['oldplacementsecret'];
127     if ( ! isset($placementsecret) ) doError("Not permitted");
128
129     $suffix = ':::' . $userid . ':::' . $placement;
130     $plaintext = $placementsecret . $suffix;
131     $hashsig = hash('sha256', $plaintext, false);
132     if ( $hashsig != $signature && isset($oldplacementsecret) && strlen($oldplacementsecret) > 1 ) {
133         $plaintext = $oldplacementsecret . $suffix;
134         $hashsig = hash('sha256', $plaintext, false);
135     }
136         
137     if ( $hashsig != $signature ) {
138         doError("Invalid sourcedid");
139     }
140
141     // Check the OAuth Signature 
142     $oauth_consumer_key = $basiclti_tool_row['resourcekey'];
143     $oauth_secret = $basiclti_tool_row['password'];
144
145     if ( ! isset($oauth_secret) ) doError("Not permitted");
146     if ( ! isset($oauth_consumer_key) ) doError("Not permitted");
147
148     // Verify the message signature
149     $store = new TrivialOAuthDataStore();
150     $store->add_consumer($oauth_consumer_key, $oauth_secret);
151
152     $server = new OAuthServer($store);
153
154     $method = new OAuthSignatureMethod_HMAC_SHA1();
155     $server->add_signature_method($method);
156     $request = OAuthRequest::from_request();
157
158     $basestring = $request->get_signature_base_string();
159
160     try {
161         $server->verify_request($request);
162     } catch (Exception $e) {
163         doError($e->getMessage());
164     }
165
166     // Beginning of actual grade processing
167     if ( $message_type == "basicoutcome" ) {
168         if ( ! isset( $basiclti_content_row['gradebook_test_id'] ) ) {
169             doError("Not permitted");
170         }
171
172         // TODO: Greg - Is this appropriate?  It would be nice to allow this.
173         if ( $atutor_course_membership_row['role'] == 'Instructor' ) {
174             doError('Grades not supported for instructors');
175         }
176
177         $gradebook_test_id = $basiclti_content_row['gradebook_test_id'];
178
179         // Check to see if this grade is in this course and member is in this course
180         // And that this grade item is of the right type
181         $sql = 'SELECT role,m.member_id AS member_id,first_name,last_name,email 
182             FROM  '.TABLE_PREFIX.'gradebook_tests AS g
183             JOIN  '.TABLE_PREFIX.'course_enrollment AS e
184             JOIN  '.TABLE_PREFIX.'members AS m 
185             ON g.course_id = e.course_id AND e.member_id = m.member_id 
186             WHERE e.course_id = '.$course_id.' AND m.member_id ='.$member_id.'
187             AND g.gradebook_test_id = '.$gradebook_test_id."
188             AND g.type = 'External' and g.grade_scale_id = 0";
189         $gradebook_result = mysql_query($sql, $db);
190         $count = mysql_num_rows($gradebook_result);
191         if ( $count < 1 ) {
192             doError("Not gradable");
193         }
194
195         $read_sql = 'SELECT d.grade AS grade
196             FROM  '.TABLE_PREFIX.'gradebook_detail AS d
197             JOIN  '.TABLE_PREFIX.'gradebook_tests AS g
198             JOIN  '.TABLE_PREFIX.'course_enrollment AS e
199             JOIN  '.TABLE_PREFIX.'members AS m 
200             ON d.gradebook_test_id = g.gradebook_test_id 
201             AND g.course_id = e.course_id AND e.member_id = m.member_id 
202             WHERE e.course_id = '.$course_id.' AND d.member_id ='.$member_id.'
203             AND g.gradebook_test_id = '.$gradebook_test_id."
204             AND g.type = 'External' and g.grade_scale_id = 0";
205
206         if ( $lti_message_type == "basic-lis-readresult" ) {
207             $grade_result = mysql_query($read_sql, $db);
208             $count = mysql_num_rows($gradebook_result);
209             if ( $count < 1 ) {
210                 doError("Not gradable");
211             }
212             unset($grade);
213             $grade_row = mysql_fetch_assoc($grade_result);
214             if ( $grade_row === false ) {
215                 // Skip
216             } else if ( isset($grade_row['grade']) ) { 
217                 $grade = $grade_row['grade'];
218             }
219
220             if ( ! isset($grade) ) {
221                 doError("Unable to read grade");
222             }
223                
224             $result = "  <result>\n" .
225                 "     <resultscore>\n" .
226                 "        <textstring>" .
227                 htmlspecialchars($grade*1.0) .
228                 "</textstring>\n" .
229                 "     </resultscore>\n" .
230                 "  </result>\n";
231             print message_response('Success', 'Status', false, "Grade read", $result);
232             exit();
233        }
234     
235         if ( $lti_message_type == "basic-lis-deleteresult" ) {
236             $delete_sql = 'DELETE FROM '.TABLE_PREFIX.'gradebook_detail 
237                 WHERE member_id ='.$member_id.'
238                 AND gradebook_test_id = '.$gradebook_test_id;
239
240             $gradebook_result = mysql_query($delete_sql, $db);
241             if ( $gradebook_result === false ) {
242                 doError("Could not delete grade");
243             }
244             print message_response('Success', 'Status', 'fullsuccess', 'Grade deleted');
245
246         } else { // Replace
247             $gradeval = -1.0;
248             if ( isset($_REQUEST['result_resultscore_textstring']) && strlen($_REQUEST['result_resultscore_textstring']) > 0) {
249                $gradeval = floatval($_REQUEST['result_resultscore_textstring']);
250             } 
251             if ( $gradeval < 0.0 || $gradeval > 1.0 ) {
252                 doError('Invalid Grade');
253             }
254
255             // TODO: Greg - do we do Insert or Update?
256             $replace_sql = 'INSERT INTO '.TABLE_PREFIX.'gradebook_detail 
257                 (gradebook_test_id, member_id, grade) VALUES
258                 ('.$gradebook_test_id.','.$member_id.','.$gradeval.')
259                 ON DUPLICATE KEY UPDATE grade='.$gradeval;
260
261             $gradebook_result = mysql_query($replace_sql, $db);
262             if ( $gradebook_result === false ) {
263                 // TODO: Log message would be good here
264                 doError("Could not store grade");
265             }
266             print message_response('Success', 'Status', 'fullsuccess', 'Grade updated');
267         }
268     
269
270     } else if ( $lti_message_type == "basic-lti-loadsetting" ) {
271         $xml = "  <setting>\n" .
272                "     <value>".htmlspecialchars($basiclti_content_row['setting'])."</value>\n" .
273                "  </setting>\n";
274         print message_response('Success', 'Status', 'fullsuccess', 'Setting retrieved', $xml);
275     } else if ( $lti_message_type == "basic-lti-savesetting" ) {
276         $setting = $_REQUEST['setting'];
277         if ( ! isset($setting) ) doError('Missing setting value');
278         // $sql = "UPDATE {$CFG->prefix}basiclti SET 
279                // setting='". mysql_escape_string($setting) . "' WHERE id=" . $basiclti->id;
280         $sql = "UPDATE ".TABLE_PREFIX."basiclti_content
281                SET setting='". mysql_escape_string($setting) . "' WHERE content_id=" . $placement;
282         $success = mysql_query($sql);
283         if ( $success ) {
284             print message_response('Success', 'Status', 'fullsuccess', 'Setting updated');
285         } else {
286             doError("Error updating setting");
287         }
288     } else if ( $lti_message_type == "basic-lti-deletesetting" ) {
289         $sql = "UPDATE ".TABLE_PREFIX."basiclti_content
290                SET setting='' WHERE content_id=" . $placement;
291         $success = mysql_query($sql);
292         if ( $success ) {
293             print message_response('Success', 'Status', 'fullsuccess', 'Setting deleted');
294         } else {
295             doError("Error updating setting");
296         }
297     } else if ( $message_type == "roster" ) {
298         $sql = 'SELECT role,m.member_id AS member_id,first_name,last_name,email 
299             FROM  '.TABLE_PREFIX.'course_enrollment AS e
300             JOIN  '.TABLE_PREFIX.'members AS m ON e.member_id = m.member_id 
301             WHERE course_id = '.$course_id;
302         $roster_result = mysql_query($sql, $db);
303         $xml = "  <memberships>\n";
304         while ($row = mysql_fetch_assoc($roster_result)) {
305             $role = "Learner";
306             if ( $row['role'] == 'Instructor' ) $role = 'Instructor';
307             $userxml = "    <member>\n".
308                        "      <user_id>".htmlspecialchars($row['member_id'])."</user_id>\n".
309                        "      <roles>$role</roles>\n";
310             if ( $basiclti_tool_row['sendname'] == 1 ||
311                  ( $basiclti_tool_row['sendname'] == 2 && $basiclti_content_row['sendname'] == 1 ) ) {
312                 if ( isset($row['first_name']) ) $userxml .=  "      <person_name_given>".htmlspecialchars($row['first_name'])."</person_name_given>\n";
313                 if ( isset($row['last_name']) ) $userxml .=  "      <person_name_family>".htmlspecialchars($row['last_name'])."</person_name_family>\n";
314             }
315             if ( $basiclti_tool_row['sendemailaddr'] == 1 ||
316                  ( $basiclti_tool_row['sendemailaddr'] == 2 && $basiclti_content_row['sendemailaddr'] == 1 ) ) {
317                 if ( isset($row['email']) ) $userxml .=  "      <person_contact_email_primary>".htmlspecialchars($row['email'])."</person_contact_email_primary>\n";
318             }
319             if ( isset($placementsecret) ) {
320                 $suffix = ':::' . $row['member_id'] . ':::' . $placement;
321                 $plaintext = $placementsecret . $suffix;
322                 $hashsig = hash('sha256', $plaintext, false);
323                 $sourcedid = $hashsig . $suffix;
324             }
325             if ( $basiclti_tool_row['acceptgrades'] == 1 && $basiclti_content_row['gradebook_test_id'] > 0 ) {
326                 if ( isset($sourcedid) ) $userxml .=  "      <lis_result_sourcedid>".htmlspecialchars($sourcedid)."</lis_result_sourcedid>\n";
327             }
328             $userxml .= "    </member>\n";
329             $xml .= $userxml;
330         }
331         $xml .= "  </memberships>\n";
332         print message_response('Success', 'Status', 'fullsuccess', 'Roster retreived', $xml);
333
334     }
335     
336 ?>