--- /dev/null
+<?php
+
+/**
+ * This class contains the callback functions that are called in the core scripts to manipulate content etc.
+ *
+ * Note:
+ * 1. CHANGE the class name and ensure its uniqueness by prefixing with the module name
+ * 2. DO NOT change the script name. Leave as "ModuleCallbacks.class.php"
+ * 3. DO NOT change the names of the methods.
+ * 4. REGISTER the unique class name in module.php
+ *
+ * @access public
+ */
+if (!defined('AT_INCLUDE_PATH')) exit;
+
+class BasicLTICallbacks {
+ /*
+ * To append output onto course content page
+ * @param: None
+ * @return: a string, plain or html, to be appended to course content page
+ */
+ public static function appendContent($cid) {
+ if ( !is_int($_SESSION['course_id']) || $_SESSION['course_id'] < 1 ) return;
+ $sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_content
+ WHERE content_id=".$cid." AND course_id = ".$_SESSION['course_id'];
+ global $db;
+ $instanceresult = mysql_query($sql, $db);
+ if ( $instanceresult == false ) return;
+ $basiclti_content_row = mysql_fetch_assoc($instanceresult);
+ if ( $basiclti_content_row === false ) return;
+ $toolid = $basiclti_content_row['toolid'];
+ $sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_tools
+ WHERE toolid='".$toolid."'";
+ $contentresult = mysql_query($sql, $db);
+ $basiclti_tool_row = mysql_fetch_assoc($contentresult);
+ if ( ! $basiclti_tool_row ) {
+ return _AT('blti_missing_tool').$toolid;
+ }
+ // Figure height
+ $height = 1200;
+ if ( isset($basiclti_tool_row['preferheight']) && $basiclti_tool_row['preferheight'] > 0 ) {
+ $height = $basiclti_tool_row['preferheight'];
+ }
+ if ( $basiclti_tool_row['allowpreferheight'] == 2 && isset($basiclti_content_row['preferheight']) && $basiclti_content_row['preferheight'] > 0 ) {
+ $height = $basiclti_content_row['preferheight'];
+ }
+
+ $myurl = AT_BASE_HREF.'mods/basiclti/launch/launch.php?cid='.$cid;
+ if ( $basiclti_tool_row['launchinpopup'] == 1 ||
+ ( $basiclti_tool_row['launchinpopup'] == 2 && $basiclti_content_row['launchinpopup'] == 1 ) ) {
+ return '<script type="text/javascript">window.open("'.$myurl.'");</script>'."\n";
+ } else {
+ return '<iframe src="'.$myurl.'" height="'.$height.'" width="100%"></iframe>'."\n";
+ }
+ }
+
+}
+
+?>
--- /dev/null
+Wed Dec 22 08:50:48 EST 2010
+
+Hi this is a alpha version of an ATutor Basic LTI Integration.
+
+First check this out from
+
+https://source.sakaiproject.org/contrib//csev/trunk/atutor/basiclti/
+
+And place this into:
+
+/atutor/docs/mods/basiclti
+
+In your ATutor distribution.
+
+Here is a video of it all working:
+
+http://www.vimeo.com/18074396
+
+Here are the steps in ATutor after the software
+(1) Install the Module from the Admin Modules Tab and Enable it
+(2) Under the Proxy Tools tab creat a new tool with the standard stuff
+ http://www.imsglobal.org/developers/BLTI/tool.php
+ lmsng.school.edu
+ secet
+(3) Make a course. Go into content and add a new content item. Give it a title
+and then select the "Tools" tab after "Surveys and Tests" - you should be able to
+pick the prxy tool you just built. Press Save and Close and it should launch.
+
+I wanted to put it in content so it looks as much as possible like what
+I expect IMS Common Cartridge 1.1 will look like since AT already has a
+nice imscc 1.0 import.
+
+/Chuck
+
--- /dev/null
+
+Need an ondelete callback from the content system.
+
+Add timeplacement based on the typical patterns of time
+in ATutor.
+
+Longer term for CC 1.1 support:
+
+Add support for XML Descriptor import
+
+Add support for global per url keys.
+
+
--- /dev/null
+Index: docs/themes/default/content.tmpl.php
+===================================================================
+--- docs/themes/default/content.tmpl.php (revision 10276)
++++ docs/themes/default/content.tmpl.php (working copy)
+@@ -95,8 +95,20 @@
+ </div>\r
+ <?php endif; ?>\r
+ \r
++<?php\r
++if (!empty($this->proxy_ids)): ?>\r
++<div id="content-proxy" class="input-form">\r
++ <strong><?php echo _AT('proxy') . ':' ; ?></strong>\r
++ <?php\r
++ foreach ($this->proxy_ids as $id ) {\r
++ echo '<iframe src="'.AT_BASE_HREF.'mods/basiclti/launch/launch.php?cid='.$id.'" height="1200" width="100%"></iframe>';\r
++ }\r
++ ?>\r
++</div>\r
++<?php endif; ?>\r
+ \r
++\r
+ <div id="content-info">\r
+ <?php echo $this->content_info; ?>\r
+ \r
+-</div>
+\ No newline at end of file
++</div>\r
+Index: docs/content.php
+===================================================================
+--- docs/content.php (revision 10276)
++++ docs/content.php (working copy)
+@@ -135,6 +135,16 @@
+ $content_forum_ids[] = $content_forum_row;
+ }
+
++// For Proxy Tools
++$content_proxy_ids = array();
++$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_content
++ WHERE content_id=".$cid;
++$contentresult = mysql_query($sql, $db);
++$row = mysql_fetch_assoc($contentresult);
++if ( $row ) {
++ $content_proxy_ids[] = $cid;
++}
++
+ // use any styles that were part of the imported document
+ // $_custom_css = $_base_href.'headstuff.php?cid='.$cid.SEP.'path='.urlEncode($_base_href.$course_base_href.$content_base_href);
+
+@@ -167,7 +177,7 @@
+
+ if ($released_status === TRUE || authenticate(AT_PRIV_CONTENT, AT_PRIV_RETURN)) {
+ //if it has test and forum associated with it, still display it even if the content is empty
+- if ($content_row['text'] == '' && (empty($content_test_ids) && empty($content_forum_ids))){
++ if ($content_row['text'] == '' && (empty($content_proxy_ids) && empty($content_test_ids) && empty($content_forum_ids))){
+ $msg->addInfo('NO_PAGE_CONTENT');
+ $savant->assign('body', '');
+ } else {
+@@ -225,6 +235,8 @@
+ $savant->assign('test_message', '');
+ $savant->assign('test_ids', array());
+ }
++
++ $savant->assign('proxy_ids', $content_proxy_ids);
+
+ /*TODO***************BOLOGNA***************REMOVE ME**********/
+ //assign forum pages if there are forums associated with this content page
+@@ -253,4 +265,4 @@
+ $_SESSION['last_visited_page'] = $server_protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+
+ require (AT_INCLUDE_PATH.'footer.inc.php');
+-?>
+\ No newline at end of file
++?>
+Index: docs/mods/_core/editor/editor_tab_functions.inc.php
+===================================================================
+--- docs/mods/_core/editor/editor_tab_functions.inc.php (revision 10276)
++++ docs/mods/_core/editor/editor_tab_functions.inc.php (working copy)
+@@ -35,6 +35,7 @@
+ $tabs[3] = array('alternative_content', 'alternatives.inc.php', 'l');
+ //Harris: Extended test functionality into content export
+ $tabs[4] = array('tests', 'tests.inc.php', 't');
++ $tabs[5] = array('tools', 'tools.inc.php', 'o');
+
+ return $tabs;
+ }
+@@ -320,6 +321,29 @@
+ }
+ }
+
++ // Add/Update The Tool
++ if ( isset($_POST['toolid']) ) {
++ $toolid = $_POST['toolid'];
++ $sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_content
++ WHERE content_id=".$_POST[cid];
++ $result = mysql_query($sql, $db);
++ if ( $toolid == '--none--' ) {
++ $sql = "DELETE FROM ". TABLE_PREFIX . "basiclti_content
++ WHERE content_id=".$_POST[cid];
++ $result = mysql_query($sql, $db);
++ } else if ( mysql_num_rows($result) == 0 ) {
++ $sql = "INSERT INTO ". TABLE_PREFIX . "basiclti_content
++ SET toolid='".$toolid."', content_id=".$_POST[cid];
++ $result = mysql_query($sql, $db);
++ if ($result===false) $msg->addError('MYSQL_FAILED');
++ } else {
++ $sql = "UPDATE ". TABLE_PREFIX . "basiclti_content
++ SET toolid='".$toolid."' WHERE content_id=".$_POST[cid];
++ $result = mysql_query($sql, $db);
++ if ($result===false) $msg->addError('MYSQL_FAILED');
++ }
++ }
++
+ //TODO*******************BOLOGNA****************REMOVE ME**************/
+ if(isset($_SESSION['associated_forum']) && !$msg->containsErrors()){
+ if($_SESSION['associated_forum']=='none'){
+Index: docs/include/header.inc.php
+===================================================================
+--- docs/include/header.inc.php (revision 10276)
++++ docs/include/header.inc.php (working copy)
+@@ -100,7 +100,11 @@
+ $savant->assign('user_name', _AT('guest'));
+ }
+
++
+ if (!isset($_pages[$current_page])) {
++print_r($_pages);
++echo($current_page);
++exit();
+ global $msg;
+ $msg->addError('PAGE_NOT_FOUND'); // probably the wrong error
+ header('location: '.AT_BASE_HREF.'index.php');
--- /dev/null
+Index: content.php
+===================================================================
+--- content.php (revision 10558)
++++ content.php (working copy)
+@@ -256,7 +256,7 @@
+ $module_list = $moduleFactory->getModules($module_status_bits, $module_type_bits, $sort = TRUE);
+ $module_contents = '';
+ foreach($module_list as $key=>$obj) {
+- $module_content = $obj->getContent();
++ $module_content = $obj->getContent($cid);
+ if (!empty($module_content)){
+ $module_contents .= '<div id="'.str_replace('/', '-', $key).'" class="content-from-module">'.$module_content.'</div>';
+ }
+@@ -280,4 +280,4 @@
+ $_SESSION['last_visited_page'] = $server_protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+
+ require (AT_INCLUDE_PATH.'footer.inc.php');
+-?>
+\ No newline at end of file
++?>
+Index: mods/_core/modules/classes/Module.class.php
+===================================================================
+--- mods/_core/modules/classes/Module.class.php (revision 10558)
++++ mods/_core/modules/classes/Module.class.php (working copy)
+@@ -711,13 +711,13 @@
+ * @author Cindy Li
+ * @date Dec 7, 2010
+ */
+- function getContent(){
+- if (file_exists(AT_MODULE_PATH . $this->_directoryName.'/moduleCallbacks.class.php') &&
++ function getContent($cid){
++ if (file_exists(AT_MODULE_PATH . $this->_directoryName.'/ModuleCallbacks.class.php') &&
+ isset($this->_callbacks[$this->_directoryName]))
+ {
+- require(AT_MODULE_PATH . $this->_directoryName.'/moduleCallbacks.class.php');
++ require(AT_MODULE_PATH . $this->_directoryName.'/ModuleCallbacks.class.php');
+ if (method_exists($this->_callbacks[$this->_directoryName], "appendContent")) {
+- eval('$output = '.$this->_callbacks[$this->_directoryName]."::appendContent();");
++ eval('$output = '.$this->_callbacks[$this->_directoryName]."::appendContent($cid);");
+ return $output;
+ }
+ }
+@@ -862,4 +862,4 @@
+ }
+ }
+ }
+-?>
+\ No newline at end of file
++?>
--- /dev/null
+/* The javascript is used in module.php @ $this->_content_tools["js"] */
+
+/*global jQuery*/
+/*global ATutor */
+/*global tinyMCE */
+/*global window */
+
+ATutor = ATutor || {};
+ATutor.mods = ATutor.mods || {};
+ATutor.mods.basiclti = ATutor.mods.basiclti || {};
+
+(function () {
+ var basicLTIOnClick = function () {
+ if ( ATutor.mods.editor.content_id == 0 ) {
+ alert("Please press save for your content item before configuring the remote tool");
+ return;
+ }
+ window.open(ATutor.base_href + 'mods/basiclti/tool/content_edit.php?cid='+ATutor.mods.editor.content_id + "&framed=1&popup=1",
+ 'newWinLTI', 'menubar=0,scrollbars=1,resizable=1,width=640,height=490');
+ return false;
+ }
+
+ //set up click handlers and show/hide appropriate tools
+ var initialize = function () {
+ jQuery("#basiclti_tool").click(basicLTIOnClick);
+ };
+
+ jQuery(document).ready(initialize);
+})();
--- /dev/null
+<?php
+
+define('AT_BL_BASENAME', 'mods/basiclti/');
+define('AT_BL_BASE', AT_INCLUDE_PATH.'../mods/basiclti/');
+define('AT_BL_INCLUDE', AT_BL_BASE.'include/');
+define('AT_BL_CONTENT_DIR', AT_CONTENT_DIR.'basiclti/');
+?>
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../include/');
+require (AT_INCLUDE_PATH.'vitals.inc.php');
+$_custom_css = $_base_path . 'mods/basiclti/module.css'; // use a custom stylesheet
+require (AT_INCLUDE_PATH.'header.inc.php');
+?>
+
+<div id="helloworld">
+ Hello Student!! :)
+</div>
+
+<?php require (AT_INCLUDE_PATH.'footer.inc.php'); ?>
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../include/');
+require (AT_INCLUDE_PATH.'vitals.inc.php');
+admin_authenticate(AT_ADMIN_PRIV_BASICLTI);
+
+if (isset($_GET['view'], $_GET['id'])) {
+ header('Location: tool/admin_view.php?id='.$_GET['id']);
+ exit;
+} else if (isset($_GET['edit'], $_GET['id'])) {
+ header('Location: tool/admin_edit.php?id='.$_GET['id']);
+ exit;
+} else if (isset($_GET['delete'], $_GET['id'])) {
+ header('Location: tool/admin_delete.php?id='.$_GET['id']);
+ exit;
+}
+
+require (AT_INCLUDE_PATH.'header.inc.php');
+
+$sql = "SELECT t.id AS id,t.title AS title,t.toolid AS toolid,
+ t.description AS description, COUNT(c.id) AS cnt
+ FROM ".TABLE_PREFIX."basiclti_tools AS t
+ LEFT OUTER JOIN ".TABLE_PREFIX."basiclti_content as c
+ ON t.toolid = c.toolid
+ WHERE t.course_id = 0 GROUP BY t.toolid ORDER BY t.title";
+$result = mysql_query($sql, $db) or die(mysql_error());
+?>
+<form name="form" method="get" action="<?php echo $_SERVER['PHP_SELF']; ?>">
+<table class="data static" summary="" rules="all">
+ <thead>
+ <th> </th>
+ <th><?php echo _AT('bl_title'); ?></th>
+ <th><?php echo _AT('bl_toolid_header'); ?></th>
+ <th><?php echo _AT('bl_description'); ?></th>
+ <th><?php echo _AT('bl_count'); ?></th>
+ </thead>
+ <tfoot>
+ <tr>
+ <td colspan="5"><input type="submit" name="view" value="<?php echo _AT('view'); ?>" />
+ <input type="submit" name="edit" value="<?php echo _AT('edit'); ?>" />
+ <input type="submit" name="delete" value="<?php echo _AT('delete'); ?>" /></td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <?php while($row = mysql_fetch_array($result)) { ?><tr>
+ <td><input type="radio" name="id" value="<?php echo $row['id']; ?>" id="m<?php echo $row['id']; ?>" /></td>
+ <td><?php echo $row['title']; ?></td>
+ <td><?php echo $row['toolid']; ?></td>
+ <td><?php echo $row['description']; ?></td>
+ <td><?php echo $row['cnt']; ?></td>
+ </tr> <?php } ?>
+ </tbody>
+</table>
+</form>
+<?php
+include(AT_INCLUDE_PATH.'footer.inc.php');
+?>
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../include/');
+require (AT_INCLUDE_PATH.'vitals.inc.php');
+authenticate(AT_PRIV_BASICLTI);
+
+if ( !is_int($_SESSION['course_id']) || $_SESSION['course_id'] < 1 ) {
+ $msg->addFeedback('NEED_COURSE_ID');
+ exit;
+}
+
+if (isset($_GET['view'], $_GET['id'])) {
+ header('Location: tool/instructor_view.php?id='.$_GET['id']);
+ exit;
+} else if (isset($_GET['edit'], $_GET['id'])) {
+ header('Location: tool/instructor_edit.php?id='.$_GET['id']);
+ exit;
+} else if (isset($_GET['delete'], $_GET['id'])) {
+ header('Location: tool/instructor_delete.php?id='.$_GET['id']);
+ exit;
+}
+
+require (AT_INCLUDE_PATH.'header.inc.php');
+
+$sql = "SELECT t.id AS id,t.title AS title,t.toolid AS toolid,
+ t.description AS description, COUNT(c.id) AS cnt
+ FROM ".TABLE_PREFIX."basiclti_tools AS t
+ LEFT OUTER JOIN ".TABLE_PREFIX."basiclti_content as c
+ ON t.toolid = c.toolid
+ WHERE t.course_id = ".$_SESSION['course_id']." GROUP BY t.toolid ORDER BY t.title";
+$result = mysql_query($sql, $db) or die(mysql_error());
+?>
+<form name="form" method="get" action="<?php echo $_SERVER['PHP_SELF']; ?>">
+<table class="data static" summary="" rules="all">
+ <thead>
+ <th> </th>
+ <th><?php echo _AT('bl_title'); ?></th>
+ <th><?php echo _AT('bl_toolid'); ?></th>
+ <th><?php echo _AT('bl_description'); ?></th>
+ <th><?php echo _AT('bl_count'); ?></th>
+ </thead>
+ <tfoot>
+ <tr>
+ <td colspan="5"><input type="submit" name="view" value="<?php echo _AT('view'); ?>" />
+ <input type="submit" name="edit" value="<?php echo _AT('edit'); ?>" />
+ <input type="submit" name="delete" value="<?php echo _AT('delete'); ?>" /></td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <?php while($row = mysql_fetch_array($result)) { ?><tr>
+ <td><input type="radio" name="id" value="<?php echo $row['id']; ?>" id="m<?php echo $row['id']; ?>" /></td>
+ <td><?php echo $row['title']; ?></td>
+ <td><?php echo $row['toolid']; ?></td>
+ <td><?php echo $row['description']; ?></td>
+ <td><?php echo $row['cnt']; ?></td>
+ </tr> <?php } ?>
+ </tbody>
+</table>
+</form>
+<?php
+include(AT_INCLUDE_PATH.'footer.inc.php');
+?>
--- /dev/null
+<?php
+$_user_location = 'users';
+define('AT_INCLUDE_PATH', '../../include/');
+require (AT_INCLUDE_PATH.'vitals.inc.php');
+$_custom_css = $_base_path . 'mods/basiclti/module.css'; // use a custom stylesheet
+require (AT_INCLUDE_PATH.'header.inc.php');
+?>
+
+<div id="helloworld">
+ This is a page of the Hello World module that requires a login session, but might contain a tool that is not a course tool :)
+</div>
+
+<?php require (AT_INCLUDE_PATH.'footer.inc.php'); ?>
--- /dev/null
+<?php
+
+$_user_location = 'public';
+
+define('AT_INCLUDE_PATH', '../../include/');
+require (AT_INCLUDE_PATH.'vitals.inc.php');
+$_custom_css = $_base_path . 'mods/basiclti/module.css'; // use a custom stylesheet
+require (AT_INCLUDE_PATH.'header.inc.php');
+?>
+
+<div id="helloworld">
+ This is a public page from the Hello World module, that does not require a login session to view. :)
+</div>
+
+<?php require (AT_INCLUDE_PATH.'footer.inc.php'); ?>
--- /dev/null
+<?php
+/**
+ * A Trivial memory-based store - no support for tokens
+ */
+class TrivialOAuthDataStore extends OAuthDataStore {
+ private $consumers = array();
+
+ function add_consumer($consumer_key, $consumer_secret) {
+ $this->consumers[$consumer_key] = $consumer_secret;
+ }
+
+ function lookup_consumer($consumer_key) {
+ if ( strpos($consumer_key, "http://" ) === 0 ) {
+ $consumer = new OAuthConsumer($consumer_key,"secret", NULL);
+ return $consumer;
+ }
+ if ( $this->consumers[$consumer_key] ) {
+ $consumer = new OAuthConsumer($consumer_key,$this->consumers[$consumer_key], NULL);
+ return $consumer;
+ }
+ return NULL;
+ }
+
+ function lookup_token($consumer, $token_type, $token) {
+ return new OAuthToken($consumer, "");
+ }
+
+ // Return NULL if the nonce has not been used
+ // Return $nonce if the nonce was previously used
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+ // Should add some clever logic to keep nonces from
+ // being reused - for no we are really trusting
+ // that the timestamp will save us
+ return NULL;
+ }
+
+ function new_request_token($consumer) {
+ return NULL;
+ }
+
+ function new_access_token($token, $consumer) {
+ return NULL;
+ }
+}
+?>
--- /dev/null
+The MIT License
+
+Copyright (c) 2007 Andy Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
--- /dev/null
+<?php
+// vim: foldmethod=marker
+
+$OAuth_last_computed_siguature = false;
+
+/* Generic exception class
+ */
+class OAuthException extends Exception {
+ // pass
+}
+
+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]";
+ }
+}
+
+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();
+ }
+}
+
+class OAuthSignatureMethod {
+ public function check_signature(&$request, $consumer, $token, $signature) {
+ $built = $this->build_signature($request, $consumer, $token);
+ return $built == $signature;
+ }
+}
+
+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
+ function get_name() {
+ return "HMAC-SHA1";
+ }
+
+ public function build_signature($request, $consumer, $token) {
+ global $OAuth_last_computed_signature;
+ $OAuth_last_computed_signature = false;
+
+ $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);
+
+ $computed_signature = base64_encode(hash_hmac('sha1', $base_string, $key, true));
+ $OAuth_last_computed_signature = $computed_signature;
+ return $computed_signature;
+ }
+
+}
+
+class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
+ public function get_name() {
+ return "PLAINTEXT";
+ }
+
+ public function build_signature($request, $consumer, $token) {
+ $sig = array(
+ OAuthUtil::urlencode_rfc3986($consumer->secret)
+ );
+
+ if ($token) {
+ array_push($sig, OAuthUtil::urlencode_rfc3986($token->secret));
+ } else {
+ array_push($sig, '');
+ }
+
+ $raw = implode("&", $sig);
+ // for debug purposes
+ $request->base_string = $raw;
+
+ return OAuthUtil::urlencode_rfc3986($raw);
+ }
+}
+
+class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
+ public function get_name() {
+ return "RSA-SHA1";
+ }
+
+ protected function fetch_public_cert(&$request) {
+ // not implemented yet, ideas are:
+ // (1) do a lookup in a table of trusted certs keyed off of consumer
+ // (2) fetch via http using a url provided by the requester
+ // (3) some sort of specific discovery code based on request
+ //
+ // either way should return a string representation of the certificate
+ throw Exception("fetch_public_cert not implemented");
+ }
+
+ protected function fetch_private_cert(&$request) {
+ // not implemented yet, ideas are:
+ // (1) do a lookup in a table of trusted certs keyed off of consumer
+ //
+ // either way should return a string representation of the certificate
+ throw Exception("fetch_private_cert not implemented");
+ }
+
+ public function build_signature(&$request, $consumer, $token) {
+ $base_string = $request->get_signature_base_string();
+ $request->base_string = $base_string;
+
+ // Fetch the private key cert based on the request
+ $cert = $this->fetch_private_cert($request);
+
+ // Pull the private key ID from the certificate
+ $privatekeyid = openssl_get_privatekey($cert);
+
+ // Sign using the key
+ $ok = openssl_sign($base_string, $signature, $privatekeyid);
+
+ // Release the key resource
+ openssl_free_key($privatekeyid);
+
+ return base64_encode($signature);
+ }
+
+ public function check_signature(&$request, $consumer, $token, $signature) {
+ $decoded_sig = base64_decode($signature);
+
+ $base_string = $request->get_signature_base_string();
+
+ // Fetch the public key cert based on the request
+ $cert = $this->fetch_public_cert($request);
+
+ // Pull the public key ID from the certificate
+ $publickeyid = openssl_get_publickey($cert);
+
+ // Check the computed signature against the one passed in the query
+ $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
+
+ // Release the key resource
+ openssl_free_key($publickeyid);
+
+ return $ok == 1;
+ }
+}
+
+class OAuthRequest {
+ private $parameters;
+ private $http_method;
+ private $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 or $parameters = array();
+ $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';
+ $port = "";
+ if ( $_SERVER['SERVER_PORT'] != "80" && $_SERVER['SERVER_PORT'] != "443" &&
+ strpos(':', $_SERVER['HTTP_HOST']) < 0 ) {
+ $port = ':' . $_SERVER['SERVER_PORT'] ;
+ }
+ @$http_url or $http_url = $scheme .
+ '://' . $_SERVER['HTTP_HOST'] .
+ $port .
+ $_SERVER['REQUEST_URI'];
+ @$http_method or $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
+ $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
+
+ $ourpost = $_POST;
+ // Deal with magic_quotes
+ // http://www.php.net/manual/en/security.magicquotes.disabling.php
+ if ( get_magic_quotes_gpc() ) {
+ $outpost = array();
+ foreach ($_POST as $k => $v) {
+ $v = stripslashes($v);
+ $ourpost[$k] = $v;
+ }
+ }
+ // Add POST Parameters if they exist
+ $parameters = array_merge($parameters, $ourpost);
+
+ // We have a Authorization-header with OAuth data. Parse the header
+ // and add those overriding any duplicates from GET or POST
+ if (@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 or $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);
+
+ // Parse the query-string to find and add GET parameters
+ $parts = parse_url($http_url);
+ if ( $parts['query'] ) {
+ $qparms = OAuthUtil::parse_parameters($parts['query']);
+ $parameters = array_merge($qparms, $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);
+
+ $port = @$parts['port'];
+ $scheme = $parts['scheme'];
+ $host = $parts['host'];
+ $path = @$parts['path'];
+
+ $port or $port = ($scheme == 'https') ? '443' : '80';
+
+ 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() {
+ $out ='Authorization: OAuth realm=""';
+ $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 .= ',' .
+ OAuthUtil::urlencode_rfc3986($k) .
+ '="' .
+ OAuthUtil::urlencode_rfc3986($v) .
+ '"';
+ }
+ 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
+ }
+}
+
+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);
+
+ $new_token = $this->data_store->new_request_token($consumer);
+
+ 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);
+
+ $new_token = $this->data_store->new_access_token($token, $consumer);
+
+ return $new_token;
+ }
+
+ /**
+ * verify an api call, checks all the parameters
+ */
+ public function verify_request(&$request) {
+ global $OAuth_last_computed_signature;
+ $OAuth_last_computed_signature = false;
+ $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) {
+ $version = 1.0;
+ }
+ if ($version && $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->get_parameter("oauth_signature_method");
+ if (!$signature_method) {
+ $signature_method = "PLAINTEXT";
+ }
+ 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->get_parameter("oauth_consumer_key");
+ 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->get_parameter('oauth_token');
+ if ( !$token_field) return false;
+ $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
+ global $OAuth_last_computed_signature;
+ $OAuth_last_computed_signature = false;
+
+ $timestamp = @$request->get_parameter('oauth_timestamp');
+ $nonce = @$request->get_parameter('oauth_nonce');
+
+ $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) {
+ $ex_text = "Invalid signature";
+ if ( $OAuth_last_computed_signature ) {
+ $ex_text = $ex_text . " ours= $OAuth_last_computed_signature yours=$signature";
+ }
+ throw new OAuthException($ex_text);
+ }
+ }
+
+ /**
+ * check that the timestamp is new enough
+ */
+ private function check_timestamp($timestamp) {
+ // verify that timestamp is recentish
+ $now = time();
+ if ($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) {
+ // 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");
+ }
+ }
+
+}
+
+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) {
+ // return a new token attached to this consumer
+ }
+
+ function new_access_token($token, $consumer) {
+ // 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
+ }
+
+}
+
+class OAuthUtil {
+ public static function urlencode_rfc3986($input) {
+ if (is_array($input)) {
+ return array_map(array('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)
+ public static function split_header($header, $only_allow_oauth_parameters = true) {
+ $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
+ $offset = 0;
+ $params = array();
+ while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
+ $match = $matches[0];
+ $header_name = $matches[2][0];
+ $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
+ if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
+ $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);
+ }
+ $offset = $match[1] + strlen($match[0]);
+ }
+
+ 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
+ return apache_request_headers();
+ }
+ // otherwise we don't have apache and are just going to have to hope
+ // that $_SERVER actually contains what we need
+ $out = array();
+ 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 = OAuthUtil::urldecode_rfc3986($split[0]);
+ $value = isset($split[1]) ? OAuthUtil::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)
+ natsort($value);
+ 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);
+ }
+}
+
+?>
--- /dev/null
+<?php
+
+require_once 'OAuth.php';
+
+// Returns true if this is a Basic LTI message
+// with minimum values to meet the protocol
+function is_basic_lti_request() {
+ $good_message_type = $_REQUEST["lti_message_type"] == "basic-lti-launch-request";
+ $good_lti_version = $_REQUEST["lti_version"] == "LTI-1p0";
+ $resource_link_id = $_REQUEST["resource_link_id"];
+ if ($good_message_type and $good_lti_version and isset($resource_link_id) ) return(true);
+ return false;
+}
+
+/**
+ * A Trivial memory-based store - no support for tokens
+ */
+class TrivialOAuthDataStore extends OAuthDataStore {
+ private $consumers = array();
+
+ function add_consumer($consumer_key, $consumer_secret) {
+ $this->consumers[$consumer_key] = $consumer_secret;
+ }
+
+ function lookup_consumer($consumer_key) {
+ if ( strpos($consumer_key, "http://" ) === 0 ) {
+ $consumer = new OAuthConsumer($consumer_key,"secret", NULL);
+ return $consumer;
+ }
+ if ( $this->consumers[$consumer_key] ) {
+ $consumer = new OAuthConsumer($consumer_key,$this->consumers[$consumer_key], NULL);
+ return $consumer;
+ }
+ return NULL;
+ }
+
+ function lookup_token($consumer, $token_type, $token) {
+ return new OAuthToken($consumer, "");
+ }
+
+ // Return NULL if the nonce has not been used
+ // Return $nonce if the nonce was previously used
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+ // Should add some clever logic to keep nonces from
+ // being reused - for no we are really trusting
+ // that the timestamp will save us
+ return NULL;
+ }
+
+ function new_request_token($consumer) {
+ return NULL;
+ }
+
+ function new_access_token($token, $consumer) {
+ return NULL;
+ }
+}
+
+
+// Basic LTI Class that does the setup and provides utility
+// functions
+class BLTI {
+
+ public $valid = false;
+ public $complete = false;
+ public $message = false;
+ public $basestring = false;
+ public $info = false;
+ public $row = false;
+ public $context_id = false; // Override context_id
+
+ function __construct($parm=false, $usesession=true, $doredirect=true) {
+
+ // If this request is not an LTI Launch, either
+ // give up or try to retrieve the context from session
+ if ( ! is_basic_lti_request() ) {
+ if ( $usesession === false ) return;
+ if ( strlen(session_id()) > 0 ) {
+ $row = $_SESSION['_basiclti_lti_row'];
+ if ( isset($row) ) $this->row = $row;
+ $context_id = $_SESSION['_basiclti_lti_context_id'];
+ if ( isset($context_id) ) $this->context_id = $context_id;
+ $info = $_SESSION['_basic_lti_context'];
+ if ( isset($info) ) {
+ $this->info = $info;
+ $this->valid = true;
+ return;
+ }
+ $this->message = "Could not find context in session";
+ return;
+ }
+ $this->message = "Session not available";
+ return;
+ }
+
+ // Insure we have a valid launch
+ if ( empty($_REQUEST["oauth_consumer_key"]) ) {
+ $this->message = "Missing oauth_consumer_key in request";
+ return;
+ }
+ $oauth_consumer_key = $_REQUEST["oauth_consumer_key"];
+
+ // Find the secret - either form the parameter as a string or
+ // look it up in a database from parameters we are given
+ $secret = false;
+ $row = false;
+ if ( is_string($parm) ) {
+ $secret = $parm;
+ } else if ( ! is_array($parm) ) {
+ $this->message = "Constructor requires a secret or database information.";
+ return;
+ } else {
+ $sql = 'SELECT * FROM '.$parm['table'].' WHERE '.
+ ($parm['key_column'] ? $parm['key_column'] : 'oauth_consumer_key').
+ '='.
+ "'".mysql_real_escape_string($oauth_consumer_key)."'";
+ $result = mysql_query($sql);
+ $num_rows = mysql_num_rows($result);
+ if ( $num_rows != 1 ) {
+ $this->message = "Your consumer is not authorized oauth_consumer_key=".$oauth_consumer_key;
+ return;
+ } else {
+ while ($row = mysql_fetch_assoc($result)) {
+ $secret = $row[$parms['secret_column']?$parms['secret_column']:'secret'];
+ $context_id = $row[$parms['context_column']?$parms['context_column']:'context_id'];
+ if ( $context_id ) $this->context_id = $context_id;
+ $this->row = $row;
+ break;
+ }
+ if ( ! is_string($secret) ) {
+ $this->message = "Could not retrieve secret oauth_consumer_key=".$oauth_consumer_key;
+ return;
+ }
+ }
+ }
+
+ // Verify the message signature
+ $store = new TrivialOAuthDataStore();
+ $store->add_consumer($oauth_consumer_key, $secret);
+
+ $server = new OAuthServer($store);
+
+ $method = new OAuthSignatureMethod_HMAC_SHA1();
+ $server->add_signature_method($method);
+ $request = OAuthRequest::from_request();
+
+ $this->basestring = $request->get_signature_base_string();
+
+ try {
+ $server->verify_request($request);
+ $this->valid = true;
+ } catch (Exception $e) {
+ $this->message = $e->getMessage();
+ return;
+ }
+
+ // Store the launch information in the session for later
+ $newinfo = array();
+ foreach($_POST as $key => $value ) {
+ if ( $key == "basiclti_submit" ) continue;
+ if ( strpos($key, "oauth_") === false ) {
+ $newinfo[$key] = $value;
+ continue;
+ }
+ if ( $key == "oauth_consumer_key" ) {
+ $newinfo[$key] = $value;
+ continue;
+ }
+ }
+
+ $this->info = $newinfo;
+ if ( $usesession == true and strlen(session_id()) > 0 ) {
+ $_SESSION['_basic_lti_context'] = $this->info;
+ unset($_SESSION['_basiclti_lti_row']);
+ unset($_SESSION['_basiclti_lti_context_id']);
+ if ( $this->row ) $_SESSION['_basiclti_lti_row'] = $this->row;
+ if ( $this->context_id ) $_SESSION['_basiclti_lti_context_id'] = $this->context_id;
+ }
+
+ if ( $this->valid && $doredirect ) {
+ $this->redirect();
+ $this->complete = true;
+ }
+ }
+
+ function addSession($location) {
+ if ( ini_get('session.use_cookies') == 0 ) {
+ if ( strpos($location,'?') > 0 ) {
+ $location = $location . '&';
+ } else {
+ $location = $location . '?';
+ }
+ $location = $location . session_name() . '=' . session_id();
+ }
+ return $location;
+ }
+
+ function isInstructor() {
+ $roles = $this->info['roles'];
+ $roles = strtolower($roles);
+ if ( ! ( strpos($roles,"instructor") === false ) ) return true;
+ if ( ! ( strpos($roles,"administrator") === false ) ) return true;
+ return false;
+ }
+
+ function getUserEmail() {
+ $email = $this->info['lis_person_contact_email_primary'];
+ if ( strlen($email) > 0 ) return $email;
+ # Sakai Hack
+ $email = $this->info['lis_person_contact_emailprimary'];
+ if ( strlen($email) > 0 ) return $email;
+ return false;
+ }
+
+ function getUserShortName() {
+ $email = $this->getUserEmail();
+ $givenname = $this->info['lis_person_name_given'];
+ $familyname = $this->info['lis_person_name_family'];
+ $fullname = $this->info['lis_person_name_full'];
+ if ( strlen($email) > 0 ) return $email;
+ if ( strlen($givenname) > 0 ) return $givenname;
+ if ( strlen($familyname) > 0 ) return $familyname;
+ return $this->getUserName();
+ }
+
+ function getUserName() {
+ $givenname = $this->info['lis_person_name_given'];
+ $familyname = $this->info['lis_person_name_family'];
+ $fullname = $this->info['lis_person_name_full'];
+ if ( strlen($fullname) > 0 ) return $fullname;
+ if ( strlen($familyname) > 0 and strlen($givenname) > 0 ) return $givenname + $familyname;
+ if ( strlen($givenname) > 0 ) return $givenname;
+ if ( strlen($familyname) > 0 ) return $familyname;
+ return $this->getUserEmail();
+ }
+
+ function getUserKey() {
+ $oauth = $this->info['oauth_consumer_key'];
+ $id = $this->info['user_id'];
+ if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id;
+ return false;
+ }
+
+ function getUserImage() {
+ $image = $this->info['user_image'];
+ if ( strlen($image) > 0 ) return $image;
+ $email = $this->getUserEmail();
+ if ( $email === false ) return false;
+ $size = 40;
+ $grav_url = $_SERVER['HTTPS'] ? 'https://' : 'http://';
+ $grav_url = $grav_url . "www.gravatar.com/avatar.php?gravatar_id=".md5( strtolower($email) )."&size=".$size;
+ return $grav_url;
+ }
+
+ function getResourceKey() {
+ $oauth = $this->info['oauth_consumer_key'];
+ $id = $this->info['resource_link_id'];
+ if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id;
+ return false;
+ }
+
+ function getResourceTitle() {
+ $title = $this->info['resource_link_title'];
+ if ( strlen($title) > 0 ) return $title;
+ return false;
+ }
+
+ function getConsumerKey() {
+ $oauth = $this->info['oauth_consumer_key'];
+ return $oauth;
+ }
+
+ function getCourseKey() {
+ if ( $this->context_id ) return $this->context_id;
+ $oauth = $this->info['oauth_consumer_key'];
+ $id = $this->info['context_id'];
+ if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id;
+ return false;
+ }
+
+ function getCourseName() {
+ $label = $this->info['context_label'];
+ $title = $this->info['context_title'];
+ $id = $this->info['context_id'];
+ if ( strlen($label) > 0 ) return $label;
+ if ( strlen($title) > 0 ) return $title;
+ if ( strlen($id) > 0 ) return $id;
+ return false;
+ }
+
+ // TODO: Add javasript version if headers are already sent
+ function redirect() {
+ $host = $_SERVER['HTTP_HOST'];
+ $uri = $_SERVER['PHP_SELF'];
+ $location = $_SERVER['HTTPS'] ? 'https://' : 'http://';
+ $location = $location . $host . $uri;
+ $location = $this->addSession($location);
+ header("Location: $location");
+ }
+
+ function dump() {
+ if ( ! $this->valid or $this->info == false ) return "Context not valid\n";
+ $ret = "";
+ if ( $this->isInstructor() ) {
+ $ret .= "isInstructor() = true\n";
+ } else {
+ $ret .= "isInstructor() = false\n";
+ }
+ $ret .= "getUserKey() = ".$this->getUserKey()."\n";
+ $ret .= "getUserEmail() = ".$this->getUserEmail()."\n";
+ $ret .= "getUserShortName() = ".$this->getUserShortName()."\n";
+ $ret .= "getUserName() = ".$this->getUserName()."\n";
+ $ret .= "getUserImage() = ".$this->getUserImage()."\n";
+ $ret .= "getResourceKey() = ".$this->getResourceKey()."\n";
+ $ret .= "getResourceTitle() = ".$this->getResourceTitle()."\n";
+ $ret .= "getCourseName() = ".$this->getCourseName()."\n";
+ $ret .= "getCourseKey() = ".$this->getCourseKey()."\n";
+ $ret .= "getConsumerKey() = ".$this->getConsumerKey()."\n";
+ return $ret;
+ }
+
+}
+
+?>
--- /dev/null
+<?php
+
+require_once 'OAuth.php';
+
+ // Replace this with some real function that pulls from the LMS.
+ function getLMSDummyData() {
+ $parms = array(
+ "resource_link_id" => "120988f929-274612",
+ "resource_link_title" => "Weekly Blog",
+ "resource_link_description" => "Each student needs to reflect on the weekly reading. These should be one paragraph long.",
+ "user_id" => "292832126",
+ "roles" => "Instructor", // or Learner
+ "lis_person_name_full" => 'Jane Q. Public',
+ "lis_person_contact_email_primary" => "user@school.edu",
+ "lis_person_sourcedid" => "school.edu:user",
+ "context_id" => "456434513",
+ "context_title" => "Design of Personal Environments",
+ "context_label" => "SI182",
+ );
+
+ return $parms;
+ }
+
+ function validateDescriptor($descriptor)
+ {
+ $xml = new SimpleXMLElement($xmldata);
+ if ( ! $xml ) {
+ echo("Error parsing Descriptor XML\n");
+ return;
+ }
+ $launch_url = $xml->secure_launch_url[0];
+ if ( ! $launch_url ) $launch_url = $xml->launch_url[0];
+ if ( $launch_url ) $launch_url = (string) $launch_url;
+ return $launch_url;
+ }
+
+ // Parse a descriptor
+ function launchInfo($xmldata) {
+ $xml = new SimpleXMLElement($xmldata);
+ if ( ! $xml ) {
+ echo("Error parsing Descriptor XML\n");
+ return;
+ }
+ $launch_url = $xml->secure_launch_url[0];
+ if ( ! $launch_url ) $launch_url = $xml->launch_url[0];
+ if ( $launch_url ) $launch_url = (string) $launch_url;
+ $custom = array();
+ if ( $xml->custom[0]->parameter )
+ foreach ( $xml->custom[0]->parameter as $resource) {
+ $key = (string) $resource['key'];
+ $key = strtolower($key);
+ $nk = "";
+ for($i=0; $i < strlen($key); $i++) {
+ $ch = substr($key,$i,1);
+ if ( $ch >= "a" && $ch <= "z" ) $nk .= $ch;
+ else if ( $ch >= "0" && $ch <= "9" ) $nk .= $ch;
+ else $nk .= "_";
+ }
+ $value = (string) $resource;
+ $custom["custom_".$nk] = $value;
+ }
+ return array("launch_url" => $launch_url, "custom" => $custom ) ;
+ }
+
+function split_custom_parameters($custom) {
+ $retval = array();
+ return merge_custom_parameters($retval, $custom);
+}
+
+function merge_custom_parameters($retval, $custom) {
+ $lines = preg_split("/[\n;]/",$custom);
+ foreach ($lines as $line){
+ $pos = strpos($line,"=");
+ if ( $pos === false || $pos < 1 ) continue;
+ $key = trim(substr($line, 0, $pos));
+ $val = trim(substr($line, $pos+1));
+ $key = 'custom_'.map_keyname($key);
+ if ( isset($retval[$key])) continue;
+ $retval[$key] = $val;
+ }
+ return $retval;
+}
+
+function map_keyname($key) {
+ $newkey = "";
+ $key = strtolower(trim($key));
+ foreach (str_split($key) as $ch) {
+ if ( ($ch >= 'a' && $ch <= 'z') || ($ch >= '0' && $ch <= '9') ) {
+ $newkey .= $ch;
+ } else {
+ $newkey .= '_';
+ }
+ }
+ return $newkey;
+}
+
+function signParameters($oldparms, $endpoint, $method, $oauth_consumer_key, $oauth_consumer_secret,
+ $submit_text = false, $org_id = false, $org_desc = false)
+{
+ global $last_base_string;
+ $parms = $oldparms;
+ if ( ! isset($parms["lti_version"]) ) $parms["lti_version"] = "LTI-1p0";
+ if ( ! isset($parms["lti_message_type"]) ) $parms["lti_message_type"] = "basic-lti-launch-request";
+ if ( ! isset($parms["oauth_callback"]) ) $parms["oauth_callback"] = "about:blank";
+ if ( $org_id ) $parms["tool_consumer_instance_guid"] = $org_id;
+ if ( $org_desc ) $parms["tool_consumer_instance_description"] = $org_desc;
+ if ( $submit_text ) $parms["ext_submit"] = $submit_text;
+
+ $test_token = '';
+
+ $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+ $test_consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL);
+
+ $acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms);
+ $acc_req->sign_request($hmac_method, $test_consumer, $test_token);
+
+ // Pass this back up "out of band" for debugging
+ $last_base_string = $acc_req->get_signature_base_string();
+
+ $newparms = $acc_req->get_parameters();
+
+ return $newparms;
+}
+
+function postLaunchHTML($newparms, $endpoint, $debug=false, $iframeattr=false) {
+ global $last_base_string;
+ $r = "<div id=\"ltiLaunchFormSubmitArea\">\n";
+ if ( $iframeattr ) {
+ $r = "<form action=\"".$endpoint."\" name=\"ltiLaunchForm\" id=\"ltiLaunchForm\" method=\"post\" target=\"basicltiLaunchFrame\" encType=\"application/x-www-form-urlencoded\">\n" ;
+ } else {
+ $r = "<form action=\"".$endpoint."\" name=\"ltiLaunchForm\" id=\"ltiLaunchForm\" method=\"post\" encType=\"application/x-www-form-urlencoded\">\n" ;
+ }
+ $submit_text = $newparms['ext_submit'];
+ foreach($newparms as $key => $value ) {
+ $key = htmlspecialchars($key);
+ $value = htmlspecialchars($value);
+ if ( $key == "ext_submit" ) {
+ $r .= "<input type=\"submit\" name=\"";
+ } else {
+ $r .= "<input type=\"hidden\" name=\"";
+ }
+ $r .= $key;
+ $r .= "\" value=\"";
+ $r .= $value;
+ $r .= "\"/>\n";
+ }
+ if ( $debug ) {
+ $r .= "<script language=\"javascript\"> \n";
+ $r .= " //<![CDATA[ \n" ;
+ $r .= "function basicltiDebugToggle() {\n";
+ $r .= " var ele = document.getElementById(\"basicltiDebug\");\n";
+ $r .= " if(ele.style.display == \"block\") {\n";
+ $r .= " ele.style.display = \"none\";\n";
+ $r .= " }\n";
+ $r .= " else {\n";
+ $r .= " ele.style.display = \"block\";\n";
+ $r .= " }\n";
+ $r .= "} \n";
+ $r .= " //]]> \n" ;
+ $r .= "</script>\n";
+ $r .= "<a id=\"displayText\" href=\"javascript:basicltiDebugToggle();\">";
+ $r .= get_string("toggle_debug_data","basiclti")."</a>\n";
+ $r .= "<div id=\"basicltiDebug\" style=\"display:none\">\n";
+ $r .= "<b>".get_string("basiclti_endpoint","basiclti")."</b><br/>\n";
+ $r .= $endpoint . "<br/>\n <br/>\n";
+ $r .= "<b>".get_string("basiclti_parameters","basiclti")."</b><br/>\n";
+ foreach($newparms as $key => $value ) {
+ $key = htmlspecialchars($key);
+ $value = htmlspecialchars($value);
+ $r .= "$key = $value<br/>\n";
+ }
+ $r .= " <br/>\n";
+ $r .= "<p><b>".get_string("basiclti_base_string","basiclti")."</b><br/>\n".$last_base_string."</p>\n";
+ $r .= "</div>\n";
+ }
+ $r .= "</form>\n";
+ if ( $iframeattr ) {
+ $r .= "<iframe name=\"basicltiLaunchFrame\" id=\"basicltiLaunchFrame\" src=\"\"\n";
+ $r .= $iframeattr . ">\n<p>".get_string("frames_required","basiclti")."</p>\n</iframe>\n";
+ }
+ if ( ! $debug ) {
+ $ext_submit = "ext_submit";
+ $ext_submit_text = $submit_text;
+ $r .= " <script type=\"text/javascript\"> \n" .
+ " //<![CDATA[ \n" .
+ " document.getElementById(\"ltiLaunchForm\").style.display = \"none\";\n" .
+ " nei = document.createElement('input');\n" .
+ " nei.setAttribute('type', 'hidden');\n" .
+ " nei.setAttribute('name', '".$ext_submit."');\n" .
+ " nei.setAttribute('value', '".$ext_submit_text."');\n" .
+ " document.getElementById(\"ltiLaunchForm\").appendChild(nei);\n" .
+ " document.ltiLaunchForm.submit(); \n" .
+ " //]]> \n" .
+ " </script> \n";
+ }
+ $r .= "</div>\n";
+ return $r;
+}
+
+/* This is a bit of homage to Moodle's pattern of internationalisation */
+function get_string($key,$bundle) {
+ return $key;
+}
+
+function do_post_request($url, $data, $optional_headers = null)
+{
+ $params = array('http' => array(
+ 'method' => 'POST',
+ 'content' => $data
+ ));
+ if ($optional_headers !== null) {
+ $params['http']['header'] = $optional_headers;
+ }
+ $ctx = stream_context_create($params);
+ $fp = @fopen($url, 'rb', false, $ctx);
+ if (!$fp) {
+ throw new Exception("Problem with $url, $php_errormsg");
+ }
+ $response = @stream_get_contents($fp);
+ if ($response === false) {
+ throw new Exception("Problem reading data from $url, $php_errormsg");
+ }
+ return $response;
+}
+
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+
+
+function loadError($message) {
+ print $message;
+ exit();
+}
+
+$cid = intval($_GET['cid']);
+
+$content_id = $cid;
+$member_id = $_SESSION['member_id'];
+require("loadrows.php");
+$course_id = $atutor_content_row['course_id'];
+// echo("basiclti_content_row<br/>\n");print_r($basiclti_content_row); echo("<hr>\n");
+// echo("basiclti_tool_row<br/>\n");print_r($basiclti_tool_row); echo("<hr>\n");
+// echo("atutor_content_row<br/>\n");print_r($atutor_content_row); echo("<hr>\n");
+// echo("atutor_course_row<br/>\n");print_r($atutor_course_row); echo("<hr>\n");
+// echo("atutor_member_row<br/>\n");print_r($atutor_member_row); echo("<hr>\n");
+// echo("atutor_course_membership_row<br/>\n");print_r($atutor_course_membership_row); echo("<hr>\n");
+
+ $lmsdata = array(
+ "resource_link_id" => $cid,
+ "resource_link_title" => $atutor_content_row['title'],
+ "resource_link_description" => $atutor_content_row['text'],
+ "user_id" => $atutor_member_row['member_id'],
+ "roles" => "Learner",
+ "launch_presentation_locale" => $_SESSION['lang'],
+ "context_id" => $atutor_course_row['course_id'],
+ "context_title" => $atutor_course_row['title'],
+ "context_label" => $atutor_course_row['title'],
+ );
+
+ $lmsdata['ext_lms'] = 'ATutor';
+
+ if ( $atutor_course_membership_row['role'] == 'Instructor' ) {
+ $lmsdata["roles"] = 'Instructor';
+ }
+
+ if ( $_SESSION['is_admin'] == 1 ) {
+ $lmsdata["roles"] = 'Instructor';
+ }
+
+ if ( $basiclti_tool_row['sendemailaddr'] == 1 ||
+ ( $basiclti_tool_row['sendemailaddr'] == 2 && $basiclti_content_row['sendemailaddr'] == 1 ) ) {
+ $lmsdata["lis_person_contact_email_primary"] = $atutor_member_row['email'];
+ }
+
+ if ( $basiclti_tool_row['sendname'] == 1 ||
+ ( $basiclti_tool_row['sendname'] == 2 && $basiclti_content_row['sendname'] == 1 ) ) {
+ $lmsdata["lis_person_name_family"] = $atutor_member_row['last_name'];
+ $lmsdata["lis_person_name_given"] = $atutor_member_row['first_name'];
+ }
+
+ $placementsecret = $basiclti_content_row['placementsecret'];
+ $sourcedid = false;
+ if ( isset($placementsecret) && strlen($placementsecret) > 0 ) {
+ $suffix = ':::' . $atutor_member_row['member_id'] . ':::' . $cid;
+ $plaintext = $placementsecret . $suffix;
+ $hashsig = hash('sha256', $plaintext, false);
+ $sourcedid = $hashsig . $suffix;
+ }
+
+ if ( $sourcedid !== false &&
+ ( $basiclti_tool_row['acceptgrades'] == 1 && $basiclti_content_row['gradebook_test_id'] != 0 ) ) {
+ $lmsdata["lis_result_sourcedid"] = $sourcedid;
+ $lmsdata["ext_ims_lis_basic_outcome_url"] = AT_BASE_HREF.'mods/basiclti/launch/service.php';
+ }
+
+ if ( $sourcedid !== false &&
+ ( $basiclti_tool_row['allowroster'] == 1 ||
+ ( $basiclti_tool_row['allowroster'] == 2 && $basiclti_content_row['allowroster'] == 1 ) ) ) {
+ $lmsdata["ext_ims_lis_memberships_id"] = $sourcedid;
+ $lmsdata["ext_ims_lis_memberships_url"] = AT_BASE_HREF.'mods/basiclti/launch/service.php';
+ }
+
+ if ( $sourcedid !== false &&
+ ( $basiclti_tool_row['allowsetting'] == 1 ||
+ ( $basiclti_tool_row['allowsetting'] == 2 && $basiclti_content_row['allowsetting'] == 1 ) ) ) {
+ $lmsdata["ext_ims_lti_tool_setting_id"] = $sourcedid;
+ $lmsdata["ext_ims_lti_tool_setting_url"] = AT_BASE_HREF.'mods/basiclti/launch/service.php';
+ $setting = $basiclti_content_row['setting'];
+ if ( isset($setting) ) {
+ $lmsdata["ext_ims_lti_tool_setting"] = $setting;
+ }
+ }
+
+require_once("ims-blti/blti_util.php");
+
+ if ( strlen($basiclti_tool_row['customparameters']) > 0 ) {
+ $lmsdata = merge_custom_parameters($lmsdata,$basiclti_tool_row['customparameters']);
+ }
+ if ( $basiclti_tool_row['customparameters'] == 1 && strlen($basiclti_content_row['customparameters']) > 0 ) {
+ $lmsdata = merge_custom_parameters($lmsdata,$basiclti_content_row['customparameters']);
+ }
+
+// print_r($lmsdata);echo("<hr>\n");
+
+$parms = $lmsdata;
+
+$endpoint = $basiclti_tool_row['toolurl'];
+$key = $basiclti_tool_row['resourcekey'];
+$secret = $basiclti_tool_row['password'];
+
+ $parms = signParameters($parms, $endpoint, "POST", $key, $secret, "Press to Launch", $tool_consumer_instance_guid, $tool_consumer_instance_description);
+
+ $debuglaunch = false;
+ if ( ( $basiclti_tool_row['debuglaunch'] == 1 ||
+ ( $basiclti_tool_row['debuglaunch'] == 2 && $basiclti_content_row['debuglaunch'] == 1 ) ) ) {
+ $debuglaunch = true;
+ }
+
+ $content = postLaunchHTML($parms, $endpoint, $debuglaunch);
+
+ print($content);
+
+
+?>
--- /dev/null
+<?php
+
+// Needs $content_id and $member_id for the BasicLTI placement
+$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_content
+ WHERE content_id=".$content_id;
+$instanceresult = mysql_query($sql, $db);
+$basiclti_content_row = mysql_fetch_assoc($instanceresult);
+if ( ! $basiclti_content_row ) {
+ loadError("Not Configured\n");
+ exit;
+}
+// echo("basiclti_content_row<br/>\n");print_r($basiclti_content_row); echo("<hr>\n");
+
+$toolid = $basiclti_content_row['toolid'];
+$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_tools
+ WHERE toolid='".$toolid."'";
+$contentresult = mysql_query($sql, $db);
+$basiclti_tool_row = mysql_fetch_assoc($contentresult);
+if ( ! $basiclti_tool_row ) {
+ loadError("Tool definition missing\n");
+ exit;
+}
+// echo("basiclti_tool_row<br/>\n");print_r($basiclti_tool_row); echo("<hr>\n");
+
+$sql = "SELECT * FROM ".TABLE_PREFIX."content
+ WHERE content_id=".$content_id;
+$contentresult = mysql_query($sql, $db);
+$atutor_content_row = mysql_fetch_assoc($contentresult);
+if ( ! $atutor_content_row ) {
+ loadError("Not Configured\n");
+ exit;
+}
+// echo("atutor_content_row<br/>\n");print_r($atutor_content_row); echo("<hr>\n");
+
+$sql = "SELECT * FROM ".TABLE_PREFIX."courses
+ WHERE course_id='".$atutor_content_row['course_id']."'";
+$courseresult = mysql_query($sql, $db);
+$atutor_course_row = mysql_fetch_assoc($courseresult);
+if ( ! $atutor_course_row ) {
+ loadError("Course definition missing\n");
+ exit;
+}
+// echo("atutor_course_row<br/>\n");print_r($atutor_course_row); echo("<hr>\n");
+
+$sql = "SELECT * FROM ".TABLE_PREFIX."course_enrollment
+ WHERE member_id='".$member_id."'";
+$enrollresult = mysql_query($sql, $db);
+$atutor_course_enrollment_row = mysql_fetch_assoc($enrollresult);
+if ( ! $atutor_course_enrollment_row ) {
+ loadError("Course enrollment missing\n");
+ exit;
+}
+// echo("atutor_course_enrollment_row<br/>\n");print_r($atutor_course_enrollment_row); echo("<hr>\n");
+
+$sql = "SELECT * FROM ".TABLE_PREFIX."members
+ WHERE member_id='".$member_id."'";
+$memberresult = mysql_query($sql, $db);
+$atutor_member_row = mysql_fetch_assoc($memberresult);
+if ( ! $atutor_member_row ) {
+ loadError("Course definition missing\n");
+ exit;
+}
+// echo("atutor_member_row<br/>\n");print_r($atutor_member_row); echo("<hr>\n");
+
+?>
--- /dev/null
+<?php
+
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'config.inc.php');
+require_once(AT_INCLUDE_PATH.'lib/mysql_connect.inc.php');
+
+ require_once("ims-blti/OAuth.php");
+ require_once("TrivialStore.php");
+
+error_reporting(E_ALL & ~E_NOTICE);
+ini_set("display_errors", 1);
+
+ 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 doError($message) {
+ print message_response('Fail', 'Error', false, $message);
+ exit();
+ }
+
+ $lti_version = $_REQUEST['lti_version'];
+ if ( $lti_version != "LTI-1p0" ) doError("Improperly formed message");
+
+ $lti_message_type = $_REQUEST['lti_message_type'];
+ if ( ! isset($lti_message_type) ) doError("Improperly formed message");
+
+ $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 ) {
+ doError("Illegal lti_message_type");
+ }
+
+ if ( !isset($sourcedid) ) {
+ doError("sourcedid missing");
+ }
+ // Truncate to maximum length
+ $sourcedid = substr($sourcedid, 0, 2048);
+
+ try {
+ $info = explode(':::',$sourcedid);
+ if ( ! is_array($info) ) doError("Bad sourcedid");
+ $signature = $info[0];
+ $userid = intval($info[1]);
+ $placement = $info[2];
+ }
+ catch(Exception $e) {
+ doError("Bad sourcedid");
+ }
+
+ if ( isset($signature) && isset($userid) && isset($placement) ) {
+ // OK
+ } else {
+ doError("Bad sourcedid");
+ }
+
+function loadError($msg) {
+ doError($msg);
+}
+
+$content_id = $placement;
+$member_id = $userid;
+require("loadrows.php");
+$course_id = $atutor_content_row['course_id'];
+// echo("basiclti_content_row<br/>\n");print_r($basiclti_content_row); echo("<hr>\n");
+// echo("basiclti_tool_row<br/>\n");print_r($basiclti_tool_row); echo("<hr>\n");
+// echo("atutor_content_row<br/>\n");print_r($atutor_content_row); echo("<hr>\n");
+// echo("atutor_course_row<br/>\n");print_r($atutor_course_row); echo("<hr>\n");
+// These two might not be important here
+// echo("atutor_member_row<br/>\n");print_r($atutor_member_row); echo("<hr>\n");
+// echo("atutor_course_membership_row<br/>\n");print_r($atutor_course_membership_row); echo("<hr>\n");
+
+ if ( $message_type == "basicoutcome" ) {
+ if ( $basiclti_tool_row['acceptgrades'] == 1 && $basiclti_content_row['gradebook_test_id'] > 0 ) {
+ // The placement is configured to accept grades
+ } else {
+ doError("Not permitted");
+ }
+ } else if ( $message_type == "roster" ) {
+ if ( $basiclti_tool_row['allowroster'] == 1 ||
+ ( $basiclti_tool_row['allowroster'] == 2 && $basiclti_content_row['allowroster'] == 1 ) ) {
+ // OK
+ } else {
+ doError("Not permitted");
+ }
+ } else if ( $message_type == "toolsetting" ) {
+ if ( $basiclti_tool_row['allowsetting'] == 1 ||
+ ( $basiclti_tool_row['allowsetting'] == 2 && $basiclti_content_row['allowsetting'] == 1 ) ) {
+ // OK
+ } else {
+ doError("Not permitted");
+ }
+ }
+
+ // Retrieve the secret we use to sign lis_result_sourcedid
+ $placementsecret = $basiclti_content_row['placementsecret'];
+ $oldplacementsecret = $basiclti_content_row['oldplacementsecret'];
+ if ( ! isset($placementsecret) ) doError("Not permitted");
+
+ $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 ) {
+ doError("Invalid sourcedid");
+ }
+
+ // Check the OAuth Signature
+ $oauth_consumer_key = $basiclti_tool_row['resourcekey'];
+ $oauth_secret = $basiclti_tool_row['password'];
+
+ if ( ! isset($oauth_secret) ) doError("Not permitted");
+ if ( ! isset($oauth_consumer_key) ) doError("Not permitted");
+
+ // 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) {
+ doError($e->getMessage());
+ }
+
+ // Beginning of actual grade processing
+ if ( $message_type == "basicoutcome" ) {
+ if ( ! isset( $basiclti_content_row['gradebook_test_id'] ) ) {
+ doError("Not permitted");
+ }
+
+ // TODO: Greg - Is this appropriate? It would be nice to allow this.
+ if ( $atutor_course_membership_row['role'] == 'Instructor' ) {
+ doError('Grades not supported for instructors');
+ }
+
+ $gradebook_test_id = $basiclti_content_row['gradebook_test_id'];
+
+ // Check to see if this grade is in this course and member is in this course
+ // And that this grade item is of the right type
+ $sql = 'SELECT role,m.member_id AS member_id,first_name,last_name,email
+ FROM '.TABLE_PREFIX.'gradebook_tests AS g
+ JOIN '.TABLE_PREFIX.'course_enrollment AS e
+ JOIN '.TABLE_PREFIX.'members AS m
+ ON g.course_id = e.course_id AND e.member_id = m.member_id
+ WHERE e.course_id = '.$course_id.' AND m.member_id ='.$member_id.'
+ AND g.gradebook_test_id = '.$gradebook_test_id."
+ AND g.type = 'External' and g.grade_scale_id = 0";
+ $gradebook_result = mysql_query($sql, $db);
+ $count = mysql_num_rows($gradebook_result);
+ if ( $count < 1 ) {
+ doError("Not gradable");
+ }
+
+ $read_sql = 'SELECT d.grade AS grade
+ FROM '.TABLE_PREFIX.'gradebook_detail AS d
+ JOIN '.TABLE_PREFIX.'gradebook_tests AS g
+ JOIN '.TABLE_PREFIX.'course_enrollment AS e
+ JOIN '.TABLE_PREFIX.'members AS m
+ ON d.gradebook_test_id = g.gradebook_test_id
+ AND g.course_id = e.course_id AND e.member_id = m.member_id
+ WHERE e.course_id = '.$course_id.' AND d.member_id ='.$member_id.'
+ AND g.gradebook_test_id = '.$gradebook_test_id."
+ AND g.type = 'External' and g.grade_scale_id = 0";
+
+ if ( $lti_message_type == "basic-lis-readresult" ) {
+ $grade_result = mysql_query($read_sql, $db);
+ $count = mysql_num_rows($gradebook_result);
+ if ( $count < 1 ) {
+ doError("Not gradable");
+ }
+ unset($grade);
+ $grade_row = mysql_fetch_assoc($grade_result);
+ if ( $grade_row === false ) {
+ // Skip
+ } else if ( isset($grade_row['grade']) ) {
+ $grade = $grade_row['grade'];
+ }
+
+ if ( ! isset($grade) ) {
+ doError("Unable to read grade");
+ }
+
+ $result = " <result>\n" .
+ " <resultscore>\n" .
+ " <textstring>" .
+ htmlspecialchars($grade*1.0) .
+ "</textstring>\n" .
+ " </resultscore>\n" .
+ " </result>\n";
+ print message_response('Success', 'Status', false, "Grade read", $result);
+ exit();
+ }
+
+ if ( $lti_message_type == "basic-lis-deleteresult" ) {
+ $delete_sql = 'DELETE FROM '.TABLE_PREFIX.'gradebook_detail
+ WHERE member_id ='.$member_id.'
+ AND gradebook_test_id = '.$gradebook_test_id;
+
+ $gradebook_result = mysql_query($delete_sql, $db);
+ if ( $gradebook_result === false ) {
+ doError("Could not delete grade");
+ }
+ print message_response('Success', 'Status', 'fullsuccess', 'Grade deleted');
+
+ } else { // Replace
+ $gradeval = -1.0;
+ if ( isset($_REQUEST['result_resultscore_textstring']) && strlen($_REQUEST['result_resultscore_textstring']) > 0) {
+ $gradeval = floatval($_REQUEST['result_resultscore_textstring']);
+ }
+ if ( $gradeval < 0.0 || $gradeval > 1.0 ) {
+ doError('Invalid Grade');
+ }
+
+ // TODO: Greg - do we do Insert or Update?
+ $replace_sql = 'INSERT INTO '.TABLE_PREFIX.'gradebook_detail
+ (gradebook_test_id, member_id, grade) VALUES
+ ('.$gradebook_test_id.','.$member_id.','.$gradeval.')
+ ON DUPLICATE KEY UPDATE grade='.$gradeval;
+
+ $gradebook_result = mysql_query($replace_sql, $db);
+ if ( $gradebook_result === false ) {
+ // TODO: Log message would be good here
+ doError("Could not store grade");
+ }
+ print message_response('Success', 'Status', 'fullsuccess', 'Grade updated');
+ }
+
+
+ } else if ( $lti_message_type == "basic-lti-loadsetting" ) {
+ $xml = " <setting>\n" .
+ " <value>".htmlspecialchars($basiclti_content_row['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) ) doError('Missing setting value');
+ // $sql = "UPDATE {$CFG->prefix}basiclti SET
+ // setting='". mysql_escape_string($setting) . "' WHERE id=" . $basiclti->id;
+ $sql = "UPDATE ".TABLE_PREFIX."basiclti_content
+ SET setting='". mysql_escape_string($setting) . "' WHERE content_id=" . $placement;
+ $success = mysql_query($sql);
+ if ( $success ) {
+ print message_response('Success', 'Status', 'fullsuccess', 'Setting updated');
+ } else {
+ doError("Error updating setting");
+ }
+ } else if ( $lti_message_type == "basic-lti-deletesetting" ) {
+ $sql = "UPDATE ".TABLE_PREFIX."basiclti_content
+ SET setting='' WHERE content_id=" . $placement;
+ $success = mysql_query($sql);
+ if ( $success ) {
+ print message_response('Success', 'Status', 'fullsuccess', 'Setting deleted');
+ } else {
+ doError("Error updating setting");
+ }
+ } else if ( $message_type == "roster" ) {
+ $sql = 'SELECT role,m.member_id AS member_id,first_name,last_name,email
+ FROM '.TABLE_PREFIX.'course_enrollment AS e
+ JOIN '.TABLE_PREFIX.'members AS m ON e.member_id = m.member_id
+ WHERE course_id = '.$course_id;
+ $roster_result = mysql_query($sql, $db);
+ $xml = " <memberships>\n";
+ while ($row = mysql_fetch_assoc($roster_result)) {
+ $role = "Learner";
+ if ( $row['role'] == 'Instructor' ) $role = 'Instructor';
+ $userxml = " <member>\n".
+ " <user_id>".htmlspecialchars($row['member_id'])."</user_id>\n".
+ " <roles>$role</roles>\n";
+ if ( $basiclti_tool_row['sendname'] == 1 ||
+ ( $basiclti_tool_row['sendname'] == 2 && $basiclti_content_row['sendname'] == 1 ) ) {
+ if ( isset($row['first_name']) ) $userxml .= " <person_name_given>".htmlspecialchars($row['first_name'])."</person_name_given>\n";
+ if ( isset($row['last_name']) ) $userxml .= " <person_name_family>".htmlspecialchars($row['last_name'])."</person_name_family>\n";
+ }
+ if ( $basiclti_tool_row['sendemailaddr'] == 1 ||
+ ( $basiclti_tool_row['sendemailaddr'] == 2 && $basiclti_content_row['sendemailaddr'] == 1 ) ) {
+ if ( isset($row['email']) ) $userxml .= " <person_contact_email_primary>".htmlspecialchars($row['email'])."</person_contact_email_primary>\n";
+ }
+ if ( isset($placementsecret) ) {
+ $suffix = ':::' . $row['member_id'] . ':::' . $placement;
+ $plaintext = $placementsecret . $suffix;
+ $hashsig = hash('sha256', $plaintext, false);
+ $sourcedid = $hashsig . $suffix;
+ }
+ if ( $basiclti_tool_row['acceptgrades'] == 1 && $basiclti_content_row['gradebook_test_id'] > 0 ) {
+ 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);
+
+ }
+
+?>
--- /dev/null
+<?php
+
+// Parse a form field description
+// field:type:key=value:key2=value2
+function parseFormString($str) {
+ $op = array();
+ $pairs = explode(":", $str);
+ foreach ($pairs as $pair) {
+ $kv = explode("=", $pair);
+ if ( sizeof($kv) == 1 ) {
+ $op[] = $pair;
+ } else {
+ $op[$kv[0]] = $kv[1];
+ }
+ }
+ return $op;
+}
+
+// Filter a form definition based on a controlling row.
+//
+// The controlling row has fields that are interpreted as
+// 0=force off, 1=force on, 2 = delegate setting
+// For radio buttons in our form, it simply checks for
+// the field of the same name in the controlling row.
+// For non-radio fields, it looks for a field in the
+// controlling row prepended by 'allow'.
+function filterForm($control_row, $fieldinfo)
+{
+ $new_form = array();
+ foreach ($fieldinfo as $line) {
+ $fields = parseFormString($line);
+ if ( $fields[1] == 'radio' ) {
+ if ( $control_row[$fields[0]] == 2 ) $new_form[] = $line;
+ }
+ // See if a non-radio field is controlled by an allow field
+ $allowfield = 'allow'.$fields[0];
+ if ( isset( $control_row[$allowfield] ) ) {
+ if ( $control_row[$allowfield] == 1 ) $new_form[] = $line;
+ }
+ }
+ return $new_form;
+}
+
+function at_form_input($row,$fieldinfo)
+{
+ $info = parseFormString($fieldinfo);
+ if ( isset($info[0]) ) $field = $info[0]; else return;
+ if ( isset($info[1]) ) $type = $info[1]; else return;
+ $label = $field;
+ if ( isset($info['label']) ) $label = $info['label'];
+ $required = isset($info['required']);
+
+ if ( $type == 'text' || $type == 'url' || $type == 'id' || $type == 'integer' ) {
+ $size = isset($info['size']) ? $info['size'] : 40; ?>
+ <div class="row">
+ <?php if ($required) { ?><span class="required" title="<?php echo _AT('required_field'); ?>">*</span><?php } ?><label for="<?php echo $field;?>"><?php echo _AT($label); ?></label><br />
+ <input type="text" id="<?php echo $field;?>" name="<?php echo $field;?>" size="<?php echo $size;?>" value="<?php echo htmlspecialchars($row[$field]); ?>" />
+ </div>
+ <?php }
+ else if ( $type == 'textarea' ) {
+ $cols = isset($info['cols']) ? $info['cols'] : 25;
+ $rows = isset($info['rows']) ? $info['rows'] : 2; ?>
+ <div class="row">
+ <?php if ($required) { ?><span class="required" title="<?php echo _AT('required_field'); ?>">*</span><?php } ?><label for="<?php echo $field;?>"><?php echo _AT($label); ?></label><br />
+ <textarea id="<?php echo $field;?>" name="<?php echo $field;?>" cols="<?php echo $cols;?>" rows="<?php echo $rows;?>"><?php echo htmlspecialchars($row[$field]); ?></textarea>
+ </div>
+ <?php }
+ else if ( $type == 'radio' ) {
+ if ( isset($info['choices']) ) {
+ $choices = explode(',', $info['choices']);
+ } else {
+ echo('<!-- at_form_radio requires choices=on,off,part -->');
+ return;
+ }
+ $current = isset($row[$field]) ? $row[$field] : -1;
+ ?>
+ <div class="row">
+ <?php if ($required) { ?><span class="required" title="<?php echo _AT('required_field'); ?>">*</span><?php } ?><label for="<?php echo $field;?>"><?php echo _AT($label); ?></label><br />
+<?php
+foreach($choices as $key => $value ) {
+$checked = '';
+if ( $key == $current ) $checked = ' checked="checked"';
+?>
+ <label><input type="radio" name="<?php echo $field; ?>" value="<?php echo $key?>" id="<?php echo $field.'_'.$value;?>"<?php echo $checked; ?>/><?php echo _AT($label.'_'.$value); ?></label><br />
+<?php } ?>
+ </div>
+<?php
+ }
+}
+
+function at_form_generate($row, $form_definition) {
+ foreach ( $form_definition as $forminput ) {
+ at_form_input($row,$forminput);
+ }
+}
+
+
+function at_form_output($row,$fieldinfo)
+{
+ $info = parseFormString($fieldinfo);
+ if ( isset($info[0]) ) $field = $info[0]; else return;
+ if ( isset($info[1]) ) $type = $info[1]; else return;
+ $label = $field;
+ if ( isset($info['label']) ) $label = $info['label'];
+
+ if ( $type == 'text' || $type == 'url' || $type == 'id' || $type == 'integer' || $type == 'textarea') {
+ if ( strlen($row[$field]) < 1 ) return; ?>
+ <div class="row">
+ <?php echo _AT($label); ?><br/>
+ <?php echo htmlspecialchars($row[$field]); ?>
+ </div>
+ <?php }
+ else if ( $type == 'radio' ) {
+ if ( isset($info['choices']) ) {
+ $choices = explode(',', $info['choices']);
+ } else {
+ echo('<!-- at_form_radio requires choices=on,off,part -->');
+ return;
+ }
+ $current = isset($row[$field]) ? $row[$field] : 0;
+ if ( $current < 0 || $current >= sizeof($choices) ) $current = 0;
+ ?>
+ <div class="row"> <?php
+ $value = $choices[$current];
+ echo _AT($label)."<br/>\n";
+ echo _AT($label.'_'.$value); ?>
+ </div>
+<?php
+ }
+}
+
+function at_form_view($row, $form_definition) {
+ foreach ( $form_definition as $forminput ) {
+ at_form_output($row,$forminput);
+ }
+}
+
+function at_form_validate($form_definition, $msg ) {
+ $retval = true;
+ $missing_fields = array();
+ $numeric_fields = array();
+ $url_fields = array();
+ $id_fields = array();
+
+ foreach ( $form_definition as $forminput ) {
+ $info = parseFormString($forminput);
+ $label = isset($info['label']) ? $info['label'] : $info[0];
+ $datafield = $_POST[$info[0]];
+ $datafield = trim($datafield);
+ // echo($info[0] . '=' . $datafield. "<br/>\n");
+ if ( isset($info['required']) && strlen($datafield) < 1 ) {
+ $missing_fields[] = _AT($label);
+ }
+ if ( $info[1] == 'integer' || $info[1] == 'radio') {
+ if ( preg_match("/[0-9]+/", $datafield) == 1 || strlen($datafield) == 0 ) {
+ // OK
+ } else {
+ $numeric_fields[] = _AT($label);
+ }
+ }
+ if ( $info[1] == 'id' ) {
+ if ( preg_match("/^[0-9a-zA-Z._-]*$/", $datafield) == 1 || strlen($datafield) == 0 ) {
+ // OK
+ } else {
+ $id_fields[] = _AT($label);
+ }
+ }
+ if ( $info[1] == 'url' ) {
+ $pattern = "'^(http://|https://)[a-z0-9][a-z0-9]*'";
+ if ( preg_match($pattern, $datafield) == 1 || strlen($datafield) == 0 ) {
+ // OK
+ } else {
+ $url_fields[] = _AT($label);
+ }
+ }
+ }
+ if (sizeof($missing_fields) > 0) {
+ $missing_fields = implode(', ', $missing_fields);
+ $msg->addError(array('EMPTY_FIELDS', $missing_fields));
+ $retval = false;
+ }
+ if (sizeof($numeric_fields) > 0) {
+ $numeric_fields = implode(', ', $numeric_fields);
+ // TODO: Make sure this prints out the list of fields
+ $msg->addError(array('NUMERIC_FIELDS', $numeric_fields));
+ $msg->addError($numeric_fields);
+ $retval = false;
+ }
+ if (sizeof($url_fields) > 0) {
+ $url_fields = implode(', ', $url_fields);
+ $msg->addError(array('URL_FIELDS', $url_fields));
+ $retval = false;
+ }
+ if (sizeof($id_fields) > 0) {
+ $id_fields = implode(', ', $id_fields);
+ $msg->addError(array('ID_FIELDS', $id_fields));
+ $retval = false;
+ }
+ return $retval;
+}
+
+function at_get_field_value($fieldvalue, $type = false) {
+ if ( $fieldvalue === false ) {
+ $fieldvalue = 'NULL';
+ } else if ( is_int($fieldvalue) ) {
+ $fieldvalue = $fieldvalue.'';
+ } else if ( $type == 'radio' || $type == 'integer') {
+ if ( strlen($fieldvalue) < 1 ) $fieldvalue = '0';
+ } else {
+ $fieldvalue = "'".mysql_real_escape_string($fieldvalue)."'";
+ }
+ return $fieldvalue;
+}
+
+// $overrides = array('course_id' => 12, "title" => "yo", "toolid" => false);
+// false in the array becomes NULL in the database
+function at_form_insert($row, $form_definition, $overrides=false) {
+ $fieldlist = "";
+ $valuelist = "";
+ $handled = array();
+ foreach ( $form_definition as $forminput ) {
+ $info = parseFormString($forminput);
+ $fieldname = $info[0];
+ $type = $info[1];
+ $fieldvalue = null;
+ if ( is_array($overrides) && isset($overrides[$fieldname]) ) $fieldvalue = $overrides[$fieldname];
+ if ( ! isset($fieldvalue) ) $fieldvalue = $row[$fieldname];
+ if ( ! isset($fieldvalue) ) continue;
+ $fieldvalue = trim($fieldvalue);
+ if ( strlen($fieldvalue) < 1 ) continue;
+ $fieldvalue = at_get_field_value($fieldvalue, $type);
+ $handled[] = $fieldname;
+ if ( $fieldlist != "" ) $fieldlist = $fieldlist.", ";
+ if ( $valuelist != "" ) $valuelist = $valuelist.", ";
+ $fieldlist = $fieldlist.$fieldname;
+ $valuelist = $valuelist.$fieldvalue;
+ }
+ if ( is_array($overrides) ) foreach($overrides as $fieldname => $fieldvalue) {
+ if ( in_array ( $fieldname , $handled) ) continue;
+ $fieldvalue = at_get_field_value($fieldvalue);
+ if ( $fieldlist != "" ) $fieldlist = $fieldlist.", ";
+ if ( $valuelist != "" ) $valuelist = $valuelist.", ";
+ $fieldlist = $fieldlist.$fieldname;
+ $valuelist = $valuelist.$fieldvalue;
+ }
+ $sql = "( $fieldlist ) VALUES ( $valuelist )";
+ return $sql;
+}
+
+function at_form_update($row, $form_definition, $overrides=false) {
+ $setlist = "";
+ $handled = array();
+ foreach ( $form_definition as $forminput ) {
+ $info = parseFormString($forminput);
+ $fieldname = $info[0];
+ $type = $info[1];
+ $fieldvalue = null;
+ if ( is_array($overrides) && isset($overrides[$fieldname]) ) $fieldvalue = $overrides[$fieldname];
+ if ( ! isset($fieldvalue) ) $fieldvalue = $row[$info[0]];
+ if ( ! isset($fieldvalue) ) $fieldvalue = '';
+ $fieldvalue = trim($fieldvalue);
+ $fieldvalue = at_get_field_value($fieldvalue, $type);
+ if ( $setlist != "" ) $setlist = $setlist.", ";
+ $setlist = $setlist.$fieldname." = ".$fieldvalue;
+ }
+ if ( is_array($overrides) ) foreach($overrides as $fieldname => $fieldvalue) {
+ if ( in_array ( $fieldname , $handled) ) continue;
+ $fieldvalue = at_get_field_value($fieldvalue);
+ if ( $setlist != "" ) $setlist = $setlist.", ";
+ $setlist = $setlist.$fieldname." = ".$fieldvalue;
+ }
+ return $setlist;
+}
+
+function foorm_i18n_util($fieldinfo) {
+ $strings = array();
+ foreach ($fieldinfo as $line) {
+ $info = parseFormString($line);
+ $label = $info[0];
+ if ( isset($info['label']) ) $label = $info['label'];
+ $strings[] = $label;
+ if ( $info[1] == 'radio' ) {
+ if ( isset($info['choices']) ) {
+ $choices = explode(',', $info['choices']);
+ foreach($choices as $choice) {
+ $strings[] = $label.'_'.$choice;
+ }
+ }
+ }
+ }
+ return $strings;
+}
+
+if ( ! function_exists('isCli') ) {
+ function isCli() {
+ $sapi_type = php_sapi_name();
+ if (substr($sapi_type, 0, 3) == 'cli' && empty($_SERVER['REMOTE_ADDR'])) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+// If we are running from the command line - do a unit test
+if ( isCli() ) {
+ print_r(parseFormString('title:text:required=true:size=25'));
+ print_r(parseFormString('description:textarea:required=true:rows=2:cols=25'));
+ print_r(parseFormString('sendemail:radio:requred=true:label=bl_sendemail:choices=on,off,part'));
+
+ $row = array();
+ $row['title'] = 'Fred';
+ $row['description'] = 'Desc';
+ $row['sendemail'] = 1;
+ function _AT($str) { return $str; }
+
+ at_form_input($row,'title:text:required=true:size=25');
+ at_form_input($row,'description:textarea:required=true:rows=2:cols=25');
+ at_form_input($row,'sendemail:radio:requred=true:label=bl_sendemail:choices=on,off,part');
+
+ $test_frm = array(
+ 'title:text:size=80',
+ 'preferheight:integer:label=bl_preferheight:size=80',
+ 'sendname:radio:label=bl_sendname:choices=off,on,content',
+ 'acceptgrades:radio:label=bl_acceptgrades:choices=off,on',
+ 'customparameters:textarea:label=bl_customparameters:rows=5:cols=25',
+ );
+
+ $i18strings = foorm_i18n_util($test_frm);
+ print_r($i18strings);
+
+}
+
--- /dev/null
+div#helloworld {
+ border: 1px solid #ccc;
+ padding: 10px;
+ width: 50%;
+ margin-right: auto;
+ margin-left: auto;
+ background-color: #efefef;
+ color: #444;
+ margin-top: 30px;
+ margin-bottom: 30px;
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*******
+ * doesn't allow this file to be loaded with a browser.
+ */
+if (!defined('AT_INCLUDE_PATH')) { exit; }
+
+error_reporting(E_ALL & ~E_NOTICE);
+ini_set("display_errors", 1);
+
+/******
+ * this file must only be included within a Module obj
+ */
+if (!isset($this) || (isset($this) && (strtolower(get_class($this)) != 'module'))) { exit(__FILE__ . ' is not a Module'); }
+
+/*******
+ * assign the instructor and admin privileges to the constants.
+ */
+define('AT_PRIV_BASICLTI', $this->getPrivilege());
+define('AT_ADMIN_PRIV_BASICLTI', $this->getAdminPrivilege());
+
+/*******
+ * set savant variable and constants
+ */
+global $savant;
+require(AT_INCLUDE_PATH.'../mods/basiclti/include/constants.inc.php');
+$savant->addPath('template', AT_BL_INCLUDE.'html/');
+
+/*******
+ * create a side menu box/stack.
+ */
+$this->_stacks['basiclti'] = array('title_var'=>'basiclti', 'file'=>'mods/basiclti/side_menu.inc.php');
+
+// the text to display on module "detail view" when sublinks are not available
+$this->_pages['mods/basiclti/index.php']['text'] = _AT('basiclti_text');
+
+/*******
+ * if this module is to be made available to students on the Home or Main Navigation.
+ */
+$_group_tool = $_student_tool = 'mods/basiclti/index.php';
+
+/*******
+ * add the admin pages when needed.
+ */
+if (admin_authenticate(AT_ADMIN_PRIV_BASICLTI, TRUE) || admin_authenticate(AT_ADMIN_PRIV_ADMIN, TRUE)) {
+ $this->_pages[AT_NAV_ADMIN] = array('mods/basiclti/index_admin.php');
+ $this->_pages['mods/basiclti/index_admin.php']['title_var'] = 'basiclti';
+ $this->_pages['mods/basiclti/index_admin.php']['parent'] = AT_NAV_ADMIN;
+ $this->_pages['mods/basiclti/index_admin.php']['children'] = array('mods/basiclti/tool/admin_create.php');
+ $this->_pages['mods/basiclti/tool/admin_create.php']['title_var'] = 'bl_create';
+ $this->_pages['mods/basiclti/tool/admin_create.php']['parent'] = 'mods/basiclti/index_admin.php';
+ $this->_pages['mods/basiclti/tool/admin_view.php']['title_var'] = 'bl_view';
+ $this->_pages['mods/basiclti/tool/admin_view.php']['parent'] = 'mods/basiclti/index_admin.php';
+ $this->_pages['mods/basiclti/tool/admin_edit.php']['title_var'] = 'bl_edit';
+ $this->_pages['mods/basiclti/tool/admin_edit.php']['parent'] = 'mods/basiclti/index_admin.php';
+ $this->_pages['mods/basiclti/tool/admin_delete.php']['title_var'] = 'bl_delete';
+ $this->_pages['mods/basiclti/tool/admin_delete.php']['parent'] = 'mods/basiclti/index_admin.php';
+}
+
+/*******
+ * instructor Manage section:
+ */
+if ( authenticate(AT_PRIV_BASICLTI, TRUE) ) {
+ $this->_pages['mods/basiclti/tool/content_edit.php']['title_var'] = 'bl_content';
+ $this->_pages['mods/basiclti/tool/content_edit.php']['parent'] = 'index.php';
+
+
+ $this->_pages['mods/basiclti/index_instructor.php']['title_var'] = 'basiclti';
+ $this->_pages['mods/basiclti/index_instructor.php']['parent'] = 'tools/index.php';
+ $this->_pages['mods/basiclti/index_instructor.php']['children'] = array('mods/basiclti/tool/instructor_create.php');
+ $this->_pages['mods/basiclti/tool/instructor_create.php']['title_var'] = 'bl_create';
+ $this->_pages['mods/basiclti/tool/instructor_create.php']['parent'] = 'mods/basiclti/index_instructor.php';
+ $this->_pages['mods/basiclti/tool/instructor_view.php']['title_var'] = 'bl_view';
+ $this->_pages['mods/basiclti/tool/instructor_view.php']['parent'] = 'mods/basiclti/index_instructor.php';
+ $this->_pages['mods/basiclti/tool/instructor_edit.php']['title_var'] = 'bl_edit';
+ $this->_pages['mods/basiclti/tool/instructor_edit.php']['parent'] = 'mods/basiclti/index_instructor.php';
+ $this->_pages['mods/basiclti/tool/instructor_delete.php']['title_var'] = 'bl_delete';
+ $this->_pages['mods/basiclti/tool/instructor_delete.php']['parent'] = 'mods/basiclti/index_instructor.php';
+}
+
+/*******
+ * student page.
+ */
+$this->_pages['mods/basiclti/index.php']['title_var'] = 'basiclti';
+$this->_pages['mods/basiclti/index.php']['img'] = 'mods/basiclti/basiclti.jpg';
+
+/* public pages */
+$this->_pages[AT_NAV_PUBLIC] = array('mods/basiclti/index_public.php');
+$this->_pages['mods/basiclti/index_public.php']['title_var'] = 'basiclti';
+$this->_pages['mods/basiclti/index_public.php']['parent'] = AT_NAV_PUBLIC;
+
+/* my start page pages */
+$this->_pages[AT_NAV_START] = array('mods/basiclti/index_mystart.php');
+$this->_pages['mods/basiclti/index_mystart.php']['title_var'] = 'basiclti';
+$this->_pages['mods/basiclti/index_mystart.php']['parent'] = AT_NAV_START;
+/* The element of content tool bar that is displayed on "Edit Content" => "Content" tab */
+
+$this->_content_tools[] = array("id"=>"basiclti_tool",
+ "class"=>"fl-col clickable",
+ "src"=>AT_BASE_HREF."mods/basiclti/images/basiclti-icon.png",
+ "title"=>_AT('basiclti_tool'),
+ "alt"=>_AT('basiclti_tool'),
+ "text"=>_AT('basiclti_content_text'),
+ "js"=>AT_BASE_HREF."mods/basiclti/content_tool_action.js");
+
+
+/*******
+ * Register the entry of the callback class. Make sure the class name is properly namespaced,
+ * for instance, prefixed with the module name, to enforce its uniqueness.
+ * This class must be defined in "ModuleCallbacks.class.php".
+ * This class is an API that contains the static methods to act on core functions.
+ */
+$this->_callbacks['basiclti'] = 'BasicLTICallbacks';
+
+function basiclti_get_group_url($group_id) {
+ return 'mods/basiclti/index.php';
+}
+
+
+?>
--- /dev/null
+# sql file for basiclti module
+
+# More Language entries at the end
+
+CREATE TABLE `basiclti_tools` (
+ `id` mediumint(10) NOT NULL AUTO_INCREMENT,
+ `toolid` varchar(32) NOT NULL,
+ `course_id` mediumint(10) NOT NULL DEFAULT '0',
+ `title` varchar(255) NOT NULL,
+ `description` varchar(1024),
+ `timecreated` TIMESTAMP,
+ `timemodified` TIMESTAMP,
+ `toolurl` varchar(1023) NOT NULL,
+ `resourcekey` varchar(1023) NOT NULL,
+ `password` varchar(1023) NOT NULL,
+ `preferheight` mediumint(4) NOT NULL DEFAULT '0',
+ `allowpreferheight` mediumint(1) NOT NULL DEFAULT '0',
+ `sendname` mediumint(1) NOT NULL DEFAULT '0',
+ `sendemailaddr` mediumint(1) NOT NULL DEFAULT '0',
+ `acceptgrades` mediumint(1) NOT NULL DEFAULT '0',
+ `allowroster` mediumint(1) NOT NULL DEFAULT '0',
+ `allowsetting` mediumint(1) NOT NULL DEFAULT '0',
+ `allowcustomparameters` mediumint(1) NOT NULL DEFAULT '0',
+ `customparameters` varchar(2048) NOT NULL,
+ `organizationid` varchar(64) NOT NULL,
+ `organizationurl` varchar(255) NOT NULL,
+ `organizationdescr` varchar(255) NOT NULL,
+ `launchinpopup` mediumint(1) NOT NULL DEFAULT '0',
+ `debuglaunch` mediumint(1) NOT NULL DEFAULT '0',
+ PRIMARY KEY ( `id`, `toolid` )
+);
+
+CREATE TABLE `basiclti_content` (
+ `id` mediumint(10) NOT NULL AUTO_INCREMENT,
+ `content_id` mediumint(10) NOT NULL DEFAULT '0',
+ `course_id` mediumint(10) NOT NULL DEFAULT '0',
+ `toolid` varchar(32) NOT NULL,
+ `preferheight` mediumint(4) NOT NULL DEFAULT '0',
+ `sendname` mediumint(1) NOT NULL DEFAULT '0',
+ `sendemailaddr` mediumint(1) NOT NULL DEFAULT '0',
+ `gradebook_test_id` mediumint(10) NOT NULL DEFAULT '0',
+ `allowroster` mediumint(1) NOT NULL DEFAULT '0',
+ `allowsetting` mediumint(1) NOT NULL DEFAULT '0',
+ `customparameters` varchar(2048) NOT NULL,
+ `launchinpopup` mediumint(1) NOT NULL DEFAULT '0',
+ `debuglaunch` mediumint(1) NOT NULL DEFAULT '0',
+ `placementsecret` varchar(1023) NOT NULL,
+ `timeplacementsecret` mediumint(10) NOT NULL DEFAULT '0',
+ `oldplacementsecret` varchar(1023) NOT NULL,
+ `setting` text(8192) NOT NULL,
+ `xmlimport` text(16384) NOT NULL,
+ PRIMARY KEY ( `id`, `course_id`, `content_id` )
+);
+
+# Language Entries
+INSERT INTO `language_text` VALUES ('en', '_module','basiclti','External Tools',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','basiclti_text','Support for integrating External Tools that support IMS Basic Learning Tools Interoperability..',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_create','Create External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_view','External Tool Settings',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_delete','Deleting External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_edit','Deleting External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_toolid_header','ToolID',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_count','Use Count',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_content_title','External Tool Settings',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','basiclti_tool','External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','basiclti_content_text','External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','basiclti_comment','You can choose and configure an External Tool associated with this Content Item.',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_choose_tool','Select External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','blti_missing_tool','External Tool configuration has is missing toolid:',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_choose_gradbook_entry','Select Gradebook Entry',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_acceptgrades','Accept Grades From External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_acceptgrades_off','Do not allow',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_acceptgrades_on','Allow',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowcustomparameters','Allow Additional Custom Parameters in Content Item',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowcustomparameters_off','Do not allow',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowcustomparameters_on','Allow',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowpreferheight','Allow Frame Height to be Changed',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowpreferheight_off','Do not allow',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowpreferheight_on','Allow',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowroster','Allow External Tool To Retrieve Roster',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowroster_content','Specify in each Content Item',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowroster_instructor','Delegate to Instructor',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowroster_off','Never',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowroster_on','Always',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowsetting','Allow External Tool to use the Setting Service',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowsetting_content','Specify in each Content Item',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowsetting_instructor','Delegate to Instructor',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowsetting_off','Never',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_allowsetting_on','Always',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_customparameters','Custom Parameters',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_debuglaunch','Launch Tool in Debug Mode',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_debuglaunch_content','Specify in each Content Item',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_debuglaunch_instructor','Delegate to Instructor',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_debuglaunch_off','Never',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_debuglaunch_on','Always',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_description','Description',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_launchinpopup','Launch Tool in Pop Up Window',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_launchinpopup_content','Specify in each Content Item',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_launchinpopup_instructor','Delegate to Instructor',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_launchinpopup_off','Never',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_launchinpopup_on','Always',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_organizationdescr','Organization Description',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_organizationid','Organization Identifier (typically DNS)',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_organizationurl','Organization URL',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_password','Tool Secret',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_preferheight','Frame Height',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_resourcekey','Tool Key (oauth_consumer_key)',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendemailaddr','Send User Mail Addresses to External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendemailaddr_content','Specify in each Content Item',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendemailaddr_instructor','Delegate to Instructor',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendemailaddr_off','Never',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendemailaddr_on','Always',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendname','Send User Names to External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendname_content','Specify in each Content Item',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendname_instructor','Delegate to Instructor',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendname_off','Never',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_sendname_on','Always',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_title','Title',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_toolid','ToolId (must be unique across system)',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_toolurl','Tool Launch URL',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_create','Create External Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_toolid_header','ToolId',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_count','Usage Count',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','bl_choose_tool','Choose Tool',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','proxy','Learning Activity',NOW(),'');
+INSERT INTO `language_text` VALUES ('en', '_module','about_content_tools','Select from the available external tools, one that can be associated with this content page as a learning activity. Or, though Manage>IMS Basic LTI add your own external tools to make them available here.',NOW(),'');
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<module version="0.1">
+ <name lang="en">External Tools</name>
+ <description lang="en">This is a module to support externally hosted tools using the IMS Basic LTI specification to launch those tools.</description>
+ <maintainers>
+ <maintainer>
+ <name>Charles Severance</name>
+ <email>csev@umich.edu</email>
+ </maintainer>
+ </maintainers>
+ <url>http://atutor.ca</url>
+ <license>BSD</license>
+ <release>
+ <version>0.1</version>
+ <date>2010-12-22</date>
+ <state>alpha</state>
+ <notes></notes>
+ </release>
+</module>
--- /dev/null
+<?php
+/* each table to be backed up. includes the sql entry and fields */
+
+$dirs = array();
+$dirs['basiclti/'] = AT_CONTENT_DIR . 'basiclti' . DIRECTORY_SEPARATOR;
+
+$sql = array();
+$sql['basiclti'] = 'SELECT value FROM '.TABLE_PREFIX.'basiclti WHERE course_id=?';
+
+function basiclti_convert($row, $course_id, $table_id_map, $version) {
+ $new_row = array();
+ $new_row[0] = $course_id;
+ $new_row[1] = $row[0];
+
+ return $new_row;
+}
+
+?>
--- /dev/null
+<?php
+/*******
+ * this function named [module_name]_cron is run by the global cron script at the module's specified
+ * interval.
+ */
+
+function basiclti_cron() {
+ global $db;
+
+ debug('yay i am running!');
+}
+
+?>
--- /dev/null
+<?php
+/*******
+ * this function named [module_name]_delete is called whenever a course content is deleted
+ * which includes when restoring a backup with override set, or when deleting an entire course.
+ * the function must delete all module-specific material associated with this course.
+ * $course is the ID of the course to delete.
+ */
+
+function basiclti_delete($course) {
+ global $db;
+
+ // delete basiclti course table entries
+ $sql = "DELETE FROM ".TABLE_PREFIX."basiclti WHERE course_id=$course";
+ mysql_query($sql, $db);
+
+ // delete basiclti course files
+ $path = AT_CONTENT_DIR .'basiclti/' . $course .'/';
+ clr_dir($path);
+}
+
+?>
--- /dev/null
+<?php
+/*******
+ * This file extends the content string manipulation.
+ * It affects the output of course content and news content at course home page.
+ * Input parameter: global variable $_input. This variable contains the input
+ * content/news string.
+ * Output: $_input. Please make sure to assign the manipulated string back to $_input.
+ */
+
+/*******
+ * Global input string. DO NOT CHANGE.
+ */
+global $_input;
+
+/*******
+ * Example, replace special tag "[black][/black]" with html
+ */
+$_input = str_replace('[black]','<span style="color: black;">',$_input);
+$_input = str_replace('[/black]','</span>',$_input);
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/*******
+ * the line below safe-guards this file from being accessed directly from
+ * a web browser. It will only execute if required from within an ATutor script,
+ * in our case the Module::install() method.
+ */
+if (!defined('AT_INCLUDE_PATH')) { exit; }
+
+/*******
+ * Note: the many options for these variables are used to decrease confusion.
+ * TRUE | FALSE | 1 will be the convention.
+ *
+ * $_course_privilege
+ * specifies the type of instructor privilege this module uses.
+ * set to empty | FALSE | 0 to disable any privileges.
+ * set to 1 | AT_PRIV_ADMIN to use the instructor only privilege.
+ * set to TRUE | 'new' to create a privilege specifically for this module:
+ * will make this module available as a student privilege.
+ *
+ * $_admin_privilege
+ * specifies the type of ATutor administrator privilege this module uses.
+ * set to FALSE | AT_ADMIN_PRIV_ADMIN to use the super administrator only privilege.
+ * set to TRUE | 'new' to create a privilege specifically for this module:
+ * will make this module available as an administrator privilege.
+ *
+ *
+ * $_cron_interval
+ * if non-zero specifies in minutes how often the module's cron job should be run.
+ * set to 0 or not set to disable.
+ */
+$_course_privilege = TRUE; // possible values: FALSE | AT_PRIV_ADMIN | TRUE
+$_admin_privilege = TRUE; // possible values: FALSE | TRUE
+$_cron_interval = 35; // run every 30 minutes
+
+/********
+ * the following code is used for creating a module-specific directory.
+ * it generates appropriate error messages to aid in its creation.
+ */
+$directory = AT_CONTENT_DIR .'basiclti';
+
+// check if the directory is writeable
+if (!is_dir($directory) && !@mkdir($directory)) {
+ $msg->addError(array('MODULE_INSTALL', '<li>'.$directory.' does not exist. Please create it.</li>'));
+} else if (!is_writable($directory) && @chmod($directory, 0666)) {
+ $msg->addError(array('MODULE_INSTALL', '<li>'.$directory.' is not writeable. On Unix issue the command <kbd>chmod a+rw</kbd>.</li>'));
+}
+
+/******
+ * the following code checks if there are any errors (generated previously)
+ * then uses the SqlUtility to run any database queries it needs, ie. to create
+ * its own tables.
+ */
+if (file_exists(dirname(__FILE__) . '/module.sql')) {
+ // deal with the SQL file:
+ require(AT_INCLUDE_PATH . 'classes/sqlutility.class.php');
+ $sqlUtility =& new SqlUtility();
+
+ /*
+ * the SQL file could be stored anywhere, and named anything, "module.sql" is simply
+ * a convention we're using.
+ */
+ $sqlUtility->queryFromFile(dirname(__FILE__) . '/module.sql', TABLE_PREFIX);
+}
+
+?>
--- /dev/null
+<?php
+/*******
+ * module_uninstall.php performs reversion of module_install.php
+ */
+
+/*******
+ * the line below safe-guards this file from being accessed directly from
+ * a web browser. It will only execute if required from within an ATutor script,
+ * in our case the Module::uninstall() method.
+ */
+if (!defined('AT_INCLUDE_PATH')) { exit; }
+
+/********
+ * the following code is used for removing a module-specific directory created in module_install.php.
+ * it generates appropriate error messages to aid in its creation.
+ */
+$directory = AT_CONTENT_DIR .'basiclti';
+
+// check if the directory exists
+if (is_dir($directory)) {
+ require(AT_INCLUDE_PATH.'../mods/_core/file_manager/filemanager.inc.php');
+
+ if (!clr_dir($directory))
+ $msg->addError(array('MODULE_UNINSTALL', '<li>'.$directory.' can not be removed. Please manually remove it.</li>'));
+}
+
+/******
+ * the following code checks if there are any errors (generated previously)
+ * then uses the SqlUtility to run reverted database queries of module.sql,
+ * ie. "create table" statement in module.sql is run as drop according table.
+ */
+if (!$msg->containsErrors() && file_exists(dirname(__FILE__) . '/module.sql')) {
+ // deal with the SQL file:
+ require(AT_INCLUDE_PATH . 'classes/sqlutility.class.php');
+ $sqlUtility = new SqlUtility();
+
+ /*
+ * the SQL file could be stored anywhere, and named anything, "module.sql" is simply
+ * a convention we're using.
+ */
+ $sqlUtility->revertQueryFromFile(dirname(__FILE__) . '/module.sql', TABLE_PREFIX);
+}
+
+?>
+
--- /dev/null
+<?php
+/* start output buffering: */
+ob_start(); ?>
+
+hello world
+
+<?php
+$savant->assign('dropdown_contents', ob_get_contents());
+ob_end_clean();
+
+$savant->assign('title', _AT('basiclti')); // the box title
+$savant->display('include/box.tmpl.php');
+?>
--- /dev/null
+<?php
+
+if (!defined('AT_INCLUDE_PATH')) { exit; }
+
+/*****
+* Free form PHP can appear here to retreive current information
+* from the module, or a text description of the module where there is
+* not current information
+*****/
+
+global $db;
+
+$link_limit = 3; // Number of links to be displayed on "detail view" box
+
+$sql = "SELECT basiclti_id, value FROM ".TABLE_PREFIX."basiclti WHERE course_id=".$_SESSION[course_id].
+ " ORDER BY value LIMIT $link_limit";
+$result = mysql_query($sql, $db);
+
+if (mysql_num_rows($result) > 0) {
+ while ($row = mysql_fetch_assoc($result)) {
+ /****
+ * SUBLINK_TEXT_LEN, VALIDATE_LENGTH_FOR_DISPLAY are defined in include/lib/constance.lib.inc
+ * SUBLINK_TEXT_LEN determins the maxium length of the string to be displayed on "detail view" box.
+ *****/
+ $list[] = '<a href="'.AT_BASE_HREF.url_rewrite('mods/basiclti/index.php?id='. $row['basiclti_id']).'"'.
+ (strlen($row['value']) > SUBLINK_TEXT_LEN ? ' title="'.$row['value'].'"' : '') .'>'.
+ validate_length($row['value'], SUBLINK_TEXT_LEN, VALIDATE_LENGTH_FOR_DISPLAY) .'</a>';
+ }
+ return $list;
+} else {
+ return 0;
+}
+
+?>
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+admin_authenticate(AT_ADMIN_PRIV_BASICLTI);
+
+require_once('forms.php');
+
+if (isset($_POST['cancel'])) {
+ $msg->addFeedback('CANCELLED');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_admin.php');
+ exit;
+} else if (isset($_POST['form_basiclti'])) {
+
+ if ( at_form_validate($blti_admin_form, $msg) ) {
+ $sql = "SELECT count(*) cnt FROM ".TABLE_PREFIX."basiclti_tools WHERE toolid = '".
+ mysql_real_escape_string($_POST['toolid'])."';";
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ $row = mysql_fetch_assoc($result);
+
+ if ($row["cnt"] != 0) {
+ $msg->addFeedback('NEED_UNIQUE_TOOLID');
+ } else {
+ $sql = at_form_insert($_POST, $blti_admin_form);
+ $sql = 'INSERT INTO '.TABLE_PREFIX."basiclti_tools ".$sql;
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ write_to_log(AT_ADMIN_LOG_INSERT, 'basiclti_create', mysql_affected_rows($db), $sql);
+ $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_admin.php');
+ exit;
+ }
+ }
+}
+
+include(AT_INCLUDE_PATH.'header.inc.php');
+
+$msg->printAll();
+
+?>
+<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" name="basiclti_form" enctype="multipart/form-data">
+ <input type="hidden" name="form_basiclti" value="true" />
+ <div class="input-form">
+ <fieldset class="group_form"><legend class="group_form"><?php echo _AT('properties'); ?></legend>
+<?php at_form_generate($_POST, $blti_admin_form); ?>
+ <div class="buttons">
+ <input type="submit" name="submit" value="<?php echo _AT('save'); ?>" accesskey="s" />
+ <input type="submit" name="cancel" value="<?php echo _AT('cancel');?>" />
+ </div>
+ </fieldset>
+ </div>
+</form>
+
+<?php
+require(AT_INCLUDE_PATH.'footer.inc.php');
--- /dev/null
+<?php
+error_reporting(E_ALL & ~E_NOTICE);
+ini_set("display_errors", 1);
+
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+admin_authenticate(AT_ADMIN_BASICLTI);
+
+$tool = intval($_REQUEST['id']);
+
+$sql = "SELECT title FROM ".TABLE_PREFIX."basiclti_tools WHERE id = ".$tool.";";
+$result = mysql_query($sql, $db) or die(mysql_error());
+$row = mysql_fetch_assoc($result);
+
+if ( strlen($row["title"]) < 1) {
+ $msg->addFeedback('UNABLE_TO_FIND_TOOL');
+ header('Location: ../index_admin.php');
+ exit;
+}
+
+if (isset($_POST['submit_no'])) {
+ $msg->addFeedback('CANCELLED');
+ header('Location: ../index_admin.php');
+ exit;
+} else if (isset($_POST['step']) && ($_POST['step'] == 2) && isset($_POST['submit_yes'])) {
+ $sql = "DELETE FROM ".TABLE_PREFIX."basiclti_tools WHERE id = ".$tool.";";
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ write_to_log(AT_ADMIN_LOG_DELETE, 'basiclti_delete', mysql_affected_rows($db), $sql);
+ $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
+ header('Location: ../index_admin.php');
+ exit;
+}
+
+require(AT_INCLUDE_PATH.'header.inc.php');
+
+if (!isset($_POST['step'])) {
+ $hidden_vars['step'] = 1;
+ $hidden_vars['id'] = $tool;
+ $msg->addConfirm(array('DELETE_TOOL_1', $row['title']), $hidden_vars);
+ $msg->printConfirm();
+} else if ($_POST['step'] == 1) {
+ $hidden_vars['step'] = 2;
+ $hidden_vars['id'] = $tool;
+ $msg->addConfirm(array('DELETE_TOOL_2', $row['title']), $hidden_vars);
+ $msg->printConfirm();
+}
+
+require(AT_INCLUDE_PATH.'footer.inc.php');
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+admin_authenticate(AT_ADMIN_PRIV_BASICLTI);
+
+require_once('forms.php');
+
+$tool = intval($_REQUEST['id']);
+
+if (isset($_POST['cancel'])) {
+ $msg->addFeedback('CANCELLED');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_admin.php');
+ exit;
+} else if (isset($_POST['form_basiclti'], $tool)) {
+
+ if ( at_form_validate($blti_admin_form, $msg) ) {
+ $sql = "SELECT count(*) cnt FROM ".TABLE_PREFIX."basiclti_tools WHERE toolid = '".
+ mysql_real_escape_string($_POST['toolid'])."' AND id != $tool;";
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ $row = mysql_fetch_assoc($result);
+
+ if ($row["cnt"] != 0) {
+ $msg->addFeedback('NEED_UNIQUE_TOOLID');
+ } else {
+ $sql = at_form_update($_POST, $blti_admin_form);
+ $sql = 'UPDATE '.TABLE_PREFIX."basiclti_tools SET ".$sql." WHERE id = $tool;";
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ write_to_log(AT_ADMIN_LOG_INSERT, 'basiclti_create', mysql_affected_rows($db), $sql);
+ $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_admin.php');
+ exit;
+ }
+ }
+}
+
+$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_tools WHERE id = ".$tool.";";
+$result = mysql_query($sql, $db) or die(mysql_error());
+$toolrow = mysql_fetch_assoc($result);
+if ( $toolrow['id'] != $tool ) {
+ $msg->addFeedback('COULD_NOT_LOAD_TOOL');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_admin.php');
+ exit;
+}
+
+include(AT_INCLUDE_PATH.'header.inc.php');
+
+$msg->printAll();
+
+?>
+<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" name="basiclti_form" enctype="multipart/form-data">
+ <input type="hidden" name="form_basiclti" value="true" />
+ <input type="hidden" name="id" value="<?php echo $tool; ?>" />
+ <div class="input-form">
+ <fieldset class="group_form"><legend class="group_form"><?php echo _AT('properties'); ?></legend>
+<?php at_form_generate($toolrow, $blti_admin_form); ?>
+ <div class="buttons">
+ <input type="submit" name="submit" value="<?php echo _AT('save'); ?>" accesskey="s" />
+ <input type="submit" name="cancel" value="<?php echo _AT('cancel');?>" />
+ </div>
+ </fieldset>
+ </div>
+</form>
+
+<?php
+require(AT_INCLUDE_PATH.'footer.inc.php');
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+admin_authenticate(AT_ADMIN_PRIV_BASICLTI);
+
+require_once('forms.php');
+
+$tool = intval($_REQUEST['id']);
+
+if (isset($_POST['done'])) {
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_admin.php');
+ exit;
+}
+
+$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_tools WHERE id = ".$tool.";";
+$result = mysql_query($sql, $db) or die(mysql_error());
+$toolrow = mysql_fetch_assoc($result);
+if ( $toolrow['id'] != $tool ) {
+ $msg->addFeedback('COULD_NOT_LOAD_TOOL');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_admin.php');
+ exit;
+}
+
+include(AT_INCLUDE_PATH.'header.inc.php');
+
+$msg->printAll();
+
+?>
+<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" name="basiclti_form" enctype="multipart/form-data">
+ <input type="hidden" name="form_basiclti" value="true" />
+ <input type="hidden" name="id" value="<?php echo $tool; ?>" />
+ <div class="input-form">
+ <fieldset class="group_form"><legend class="group_form"><?php echo _AT('properties'); ?></legend>
+<?php at_form_view($toolrow, $blti_admin_form); ?>
+ <div class="buttons">
+ <input type="submit" name="done" value="<?php echo _AT('done');?>" />
+ </div>
+ </fieldset>
+ </div>
+</form>
+
+<?php
+require(AT_INCLUDE_PATH.'footer.inc.php');
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+authenticate(AT_PRIV_BASICLTI);
+
+require_once('forms.php');
+
+if ( !is_int($_SESSION['course_id']) || $_SESSION['course_id'] < 1 ) {
+ $msg->addFeedback('NEED_COURSE_ID');
+ exit;
+}
+
+// Add/Update The Tool
+if ( isset($_POST['toolid']) && at_form_validate($blti_content_edit_form, $msg)) {
+ $toolid = $_POST['toolid']; // Escaping is done in the at_form_util code
+ $sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_content
+ WHERE content_id=".$_POST[cid]." AND course_id=".$_SESSION[course_id];
+ $result = mysql_query($sql, $db);
+ if ( $toolid == '--none--' ) {
+ $sql = "DELETE FROM ". TABLE_PREFIX . "basiclti_content
+ WHERE content_id=".$_POST[cid]." AND
+ course_id=".$_SESSION[course_id];
+ $result = mysql_query($sql, $db);
+ if ($result===false) {
+ $msg->addError('MYSQL_FAILED');
+ } else {
+ $msg->addFeedback('BASICLTI_DELETED');
+ }
+ } else if ( mysql_num_rows($result) == 0 ) {
+ $sql = "INSERT INTO ". TABLE_PREFIX . "basiclti_content
+ SET toolid='".$toolid."', content_id=".$_POST[cid].",
+ course_id=".$_SESSION[course_id];
+ $result = mysql_query($sql, $db);
+ if ($result===false) {
+ $msg->addError('MYSQL_FAILED');
+ } else {
+ $msg->addFeedback('BASICLTI_SAVED');
+ }
+
+ } else if ( $result !== false ) {
+ $gradebook_test_id = 0;
+ $basiclti_content_row = mysql_fetch_assoc($result);
+ $placementsecret = $basiclti_content_row['placementsecret'];
+ $gradebook_check = intval($_POST['gradebook_test_id']);
+ if ( isset($_POST['gradebook_test_id']) && $gradebook_check > 0 ) {
+ $gradebook_test_id = $gradebook_check;
+ $sql = "SELECT g.gradebook_test_id AS id, g.title AS title
+ FROM ".TABLE_PREFIX."gradebook_tests AS g
+ WHERE g.course_id = ".$_SESSION[course_id]."
+ AND g.type = 'External' and g.grade_scale_id = 0
+ AND gradebook_test_id = ".$gradebook_test_id;
+ $result = mysql_query($sql, $db);
+ if ( $result === false ) {
+ $gradebook_test_id = 0;
+ } else {
+ if ( strlen($placementsecret) < 1 ) {
+ $placementsecret = uniqid("bl",true);
+ }
+ }
+ }
+ // Override these fields (don't take from form)
+ $fields = array('toolid' => $toolid, 'gradebook_test_id' => $gradebook_test_id,
+ 'placementsecret' => $placementsecret);
+ $sql = at_form_update($_POST, $blti_content_edit_form, $fields);
+ $sql = "UPDATE ". TABLE_PREFIX . "basiclti_content
+ SET ".$sql." WHERE content_id=".$_POST[cid]." AND
+ course_id=".$_SESSION[course_id];
+ $result = mysql_query($sql, $db);
+ if ($result===false) {
+ $msg->addError('MYSQL_FAILED');
+ } else {
+ $msg->addFeedback('BASICLTI_SAVED');
+ }
+ }
+}
+
+// echo("<hr>$sql<hr>\n");
+
+$cid = intval($_REQUEST['cid']);
+
+global $framed, $popup;
+
+if ((isset($_REQUEST['popup']) && $_REQUEST['popup']) &&
+ (!isset($_REQUEST['framed']) || !$_REQUEST['framed'])) {
+ $popup = TRUE;
+ $framed = FALSE;
+} elseif (isset($_REQUEST['framed']) && $_REQUEST['framed'] && isset($_REQUEST['popup']) && $_REQUEST['popup']) {
+ $popup = TRUE;
+ $framed = TRUE;
+ $tool_flag = TRUE;
+} else {
+ $popup = FALSE;
+ $framed = FALSE;
+}
+
+require(AT_INCLUDE_PATH.'header.inc.php');
+
+/* get a list of all the tools, we have */
+$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_tools WHERE course_id = 0".
+ " OR course_id=".$_SESSION[course_id]." ORDER BY course_id,title";
+
+$toolresult = mysql_query($sql, $db);
+$num_tools = mysql_num_rows($toolresult);
+
+//If there are no Tools, don't display anything except a message
+if ($num_tools == 0){
+ $msg->addInfo('NO_PROXY_TOOLS');
+ $msg->printInfos();
+ return;
+}
+
+?>
+<div class="input-form">
+
+<form name="datagrid" action="" method="POST">
+
+<fieldset class="group_form">
+ <legend class="group_form"><?php echo _AT('bl_content_title'); ?></legend>
+<br/>
+<?php echo _AT('basiclti_comment');?>
+<br/>
+<?php echo $msg->printFeedbacks();
+
+// Get the current content item
+$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_content
+ WHERE content_id=$cid";
+$contentresult = mysql_query($sql, $db);
+$basiclti_content_row = mysql_fetch_assoc($contentresult);
+// if ( $basiclti_content_row ) echo("FOUND"); else echo("NOT");
+?>
+<div class="row">
+ <?php echo _AT('bl_choose_tool'); ?><br/>
+ <select id="toolid" name="toolid" onchange="datagrid.submit();">
+ <option value="--none--"> </option><?php
+ $basiclti_tool_row = false;
+ $found = false; // Only the first one
+ while ( $tool = mysql_fetch_assoc($toolresult) ) {
+ $selected = "";
+ if ( ! $found && $tool['toolid'] == $basiclti_content_row['toolid'] ) {
+ $selected = ' selected="yes"';
+ $basiclti_tool_row = $tool;
+ $found = true;
+ }
+ echo '<option value="'.$tool['toolid'].'"'.$selected.'>'.$tool['title']."</option>\n";
+ } ?>
+ </select>
+<div>
+<?php
+if ( $basiclti_tool_row != false && $basiclti_tool_row['acceptgrades'] == 1 ) {
+ $sql = "SELECT g.gradebook_test_id AS id, g.title AS title
+ FROM ".TABLE_PREFIX."gradebook_tests AS g
+ WHERE g.course_id = ".$_SESSION[course_id]."
+ AND g.type = 'External' and g.grade_scale_id = 0";
+ $graderesult = mysql_query($sql, $db);
+ if ( $graderesult !== false && mysql_num_rows($graderesult) > 0) { ?>
+<div class="row">
+ <?php echo _AT('bl_choose_gradbook_entry'); ?><br/>
+ <select id="gradebook_test_id" name="gradebook_test_id">
+ <option value="--none--"> </option><?php
+ while ( $gradeitem = mysql_fetch_assoc($graderesult) ) {
+ echo($gradeitem['title']);
+ $selected = "";
+ if ( $gradeitem['id'] == $basiclti_content_row['gradebook_test_id'] ) {
+ $selected = ' selected="yes"';
+ }
+ echo '<option value="'.$gradeitem['id'].'"'.$selected.'>'.$gradeitem['title']."</option>\n";
+ } ?>
+ </select>
+</div> <?php
+ }
+}
+?>
+ <input type="hidden" name="cid" value="<?php echo($cid);?>" />
+<?php
+if ( $basiclti_tool_row !== false ) {
+ $blti_content_edit_form = filterForm($basiclti_tool_row, $blti_content_edit_form);
+ at_form_generate($basiclti_content_row, $blti_content_edit_form);
+ echo('<input type="submit" name="save" value="Save" class="button" />'."\n");
+}
+?>
+</div>
+</legend>
+</form>
+</div>
+<?php echo("<hr><pre>\n");print_r($basiclti_tool_row); echo("\n</pre>\n"); ?>
+<?php require(AT_INCLUDE_PATH.'footer.inc.php'); ?>
--- /dev/null
+<?php
+
+require_once('../lib/at_form_util.php');
+
+$blti_instructor_form = array(
+ 'title:text:label=bl_title:required=true:size=25',
+ 'toolid:id:label=bl_toolid:required=true:size=16',
+ 'description:textarea:label=bl_description:required=true:rows=2:cols=25',
+ 'toolurl:url:label=bl_toolurl:required=true:size=80',
+ 'resourcekey:text:label=bl_resourcekey:required=true:size=80',
+ 'password:text:required=true:label=bl_password:size=80',
+ 'preferheight:integer:label=bl_preferheight:size=80',
+ 'allowpreferheight:radio:label=bl_allowpreferheight:choices=off,on',
+ 'launchinpopup:radio:label=bl_launchinpopup:choices=off,on,content',
+ 'debuglaunch:radio:label=bl_debuglaunch:choices=off,on,content',
+ 'sendname:radio:label=bl_sendname:choices=off,on,content',
+ 'sendemailaddr:radio:label=bl_sendemailaddr:choices=off,on,content',
+ 'acceptgrades:radio:label=bl_acceptgrades:choices=off,on',
+ 'allowroster:radio:label=bl_allowroster:choices=off,on,content',
+ 'allowsetting:radio:label=bl_allowsetting:choices=off,on,content',
+ 'allowcustomparameters:radio:label=bl_allowcustomparameters:choices=off,on',
+ 'customparameters:textarea:label=bl_customparameters:rows=5:cols=25',
+ );
+
+$blti_admin_form = array();
+foreach ( $blti_instructor_form as $line ) {
+ $newline = str_replace('choices=off,on,content','choices=off,on,instructor',$line);
+ $blti_admin_form[] = $newline;
+}
+
+$blti_admin_form = array_merge($blti_admin_form, array(
+ 'organizationid:text:label=bl_organizationid:size=80',
+ 'organizationurl:text:label=bl_organizationurl:size=80',
+ 'organizationdescr:text:label=bl_organizationdescr:size=80',
+ ) );
+
+$blti_content_edit_form = array(
+ 'preferheight:integer:label=bl_preferheight:size=80',
+ 'launchinpopup:radio:label=bl_launchinpopup:choices=off,on',
+ 'debuglaunch:radio:label=bl_debuglaunch:choices=off,on',
+ 'sendname:radio:label=bl_sendname:choices=off,on',
+ 'sendemailaddr:radio:label=bl_sendemailaddr:choices=off,on',
+ 'allowroster:radio:label=bl_allowroster:choices=off,on',
+ 'allowsetting:radio:label=bl_allowsetting:choices=off,on',
+ 'customparameters:textarea:label=bl_customparameters:rows=5:cols=25',
+ );
+
+if ( ! function_exists('isCli') ) {
+ function isCli() {
+ $sapi_type = php_sapi_name();
+ if (substr($sapi_type, 0, 3) == 'cli' && empty($_SERVER['REMOTE_ADDR'])) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+// If we are running from the command line - do a unit test
+if ( isCli() ) {
+ function startsWith($haystack,$needle,$case=true) {
+ if($case){return (strcmp(substr($haystack, 0, strlen($needle)),$needle)===0);}
+ return (strcasecmp(substr($haystack, 0, strlen($needle)),$needle)===0);
+ }
+
+ function endsWith($haystack,$needle,$case=true) {
+ if($case){return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);}
+ return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);
+ }
+
+ $i18nstrings = array_merge(foorm_i18n_util($blti_instructor_form),
+ foorm_i18n_util($blti_admin_form),
+ foorm_i18n_util($blti_content_edit_form));
+ $i18nstrings = array_unique($i18nstrings);
+ sort($i18nstrings);
+ foreach ($i18nstrings as $i18n ) {
+ $value = $i18n;
+ if ( startsWith($value,"bl_") ) $value = substr($value,3);
+ if ( endsWith($value,"_on") ) $value = 'Always enabled';
+ if ( endsWith($value,"_off") ) $value = 'Never allowed';
+ if ( endsWith($value,"_instructor") ) $value = 'Delegate to Instructor';
+ if ( endsWith($value,"_content") ) $value = 'Specify in each Content Item';
+ $value = ucfirst($value);
+
+ echo("INSERT INTO 'language_text' VALUES ('en', '_module','$i18n','$value',NOW(),'');\n");
+ }
+}
+?>
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+authenticate(AT_PRIV_BASICLTI);
+
+require_once('forms.php');
+
+if ( !is_int($_SESSION['course_id']) || $_SESSION['course_id'] < 1 ) {
+ $msg->addFeedback('NEED_COURSE_ID');
+ exit;
+}
+
+if (isset($_POST['cancel'])) {
+ $msg->addFeedback('CANCELLED');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_instructor.php');
+ exit;
+} else if (isset($_POST['form_basiclti'])) {
+
+ if ( at_form_validate($blti_instructor_form, $msg) ) {
+ $sql = "SELECT count(*) cnt FROM ".TABLE_PREFIX."basiclti_tools WHERE toolid = '".
+ mysql_real_escape_string($_POST['toolid'])."' AND course_id = ". $_SESSION['course_id'];
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ $row = mysql_fetch_assoc($result);
+
+ if ($row["cnt"] != 0) {
+ $msg->addFeedback('NEED_UNIQUE_TOOLID');
+ } else {
+ $fields = array('course_id' => $_SESSION['course_id']);
+ $sql = at_form_insert($_POST, $blti_instructor_form, $fields);
+ $sql = 'INSERT INTO '.TABLE_PREFIX."basiclti_tools ".$sql;
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ write_to_log(AT_ADMIN_LOG_INSERT, 'basiclti_create', mysql_affected_rows($db), $sql);
+ $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_instructor.php');
+ exit;
+ }
+ }
+}
+
+include(AT_INCLUDE_PATH.'header.inc.php');
+
+$msg->printAll();
+
+?>
+<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" name="basiclti_form" enctype="multipart/form-data">
+ <input type="hidden" name="form_basiclti" value="true" />
+ <div class="input-form">
+ <fieldset class="group_form"><legend class="group_form"><?php echo _AT('properties'); ?></legend>
+<?php at_form_generate($_POST, $blti_instructor_form); ?>
+ <div class="buttons">
+ <input type="submit" name="submit" value="<?php echo _AT('save'); ?>" accesskey="s" />
+ <input type="submit" name="cancel" value="<?php echo _AT('cancel');?>" />
+ </div>
+ </fieldset>
+ </div>
+</form>
+
+<?php
+require(AT_INCLUDE_PATH.'footer.inc.php');
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+authenticate(AT_PRIV_BASICLTI);
+
+if ( !is_int($_SESSION['course_id']) || $_SESSION['course_id'] < 1 ) {
+ $msg->addFeedback('NEED_COURSE_ID');
+ exit;
+}
+
+$tool = intval($_REQUEST['id']);
+
+$sql = "SELECT title FROM ".TABLE_PREFIX."basiclti_tools WHERE id = ".$tool.
+ " AND course_id = ". $_SESSION['course_id'];
+$result = mysql_query($sql, $db) or die(mysql_error());
+$row = mysql_fetch_assoc($result);
+
+if ( strlen($row["title"]) < 1) {
+ $msg->addFeedback('UNABLE_TO_FIND_TOOL');
+ header('Location: ../index_instructor.php');
+ exit;
+}
+
+if (isset($_POST['submit_no'])) {
+ $msg->addFeedback('CANCELLED');
+ header('Location: ../index_instructor.php');
+ exit;
+} else if (isset($_POST['step']) && ($_POST['step'] == 2) && isset($_POST['submit_yes'])) {
+ $sql = "DELETE FROM ".TABLE_PREFIX."basiclti_tools WHERE id = ".$tool.
+ " AND course_id = ". $_SESSION['course_id'];
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ write_to_log(AT_ADMIN_LOG_DELETE, 'basiclti_delete', mysql_affected_rows($db), $sql);
+ $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
+ header('Location: ../index_instructor.php');
+ exit;
+}
+
+require(AT_INCLUDE_PATH.'header.inc.php');
+
+if (!isset($_POST['step'])) {
+ $hidden_vars['step'] = 1;
+ $hidden_vars['id'] = $tool;
+ $msg->addConfirm(array('DELETE_TOOL_1', $row['title']), $hidden_vars);
+ $msg->printConfirm();
+} else if ($_POST['step'] == 1) {
+ $hidden_vars['step'] = 2;
+ $hidden_vars['id'] = $tool;
+ $msg->addConfirm(array('DELETE_TOOL_2', $row['title']), $hidden_vars);
+ $msg->printConfirm();
+}
+
+require(AT_INCLUDE_PATH.'footer.inc.php');
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+authenticate(AT_PRIV_BASICLTI);
+
+if ( !is_int($_SESSION['course_id']) || $_SESSION['course_id'] < 1 ) {
+ $msg->addFeedback('NEED_COURSE_ID');
+ exit;
+}
+
+require_once('forms.php');
+
+$tool = intval($_REQUEST['id']);
+
+if (isset($_POST['cancel'])) {
+ $msg->addFeedback('CANCELLED');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_instructor.php');
+ exit;
+} else if (isset($_POST['form_basiclti'], $tool)) {
+
+ if ( at_form_validate($blti_instructor_form, $msg) ) {
+ $sql = "SELECT count(*) cnt FROM ".TABLE_PREFIX."basiclti_tools WHERE toolid = '".
+ mysql_real_escape_string($_POST['toolid'])."' AND id != $tool".
+ " AND course_id = ". $_SESSION['course_id'];
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ $row = mysql_fetch_assoc($result);
+
+ if ($row["cnt"] != 0) {
+ $msg->addFeedback('NEED_UNIQUE_TOOLID');
+ } else {
+ $fields = array('course_id' => $_SESSION['course_id']);
+ $sql = at_form_update($_POST, $blti_instructor_form, $fields);
+ $sql = 'UPDATE '.TABLE_PREFIX."basiclti_tools SET ".$sql." WHERE id = $tool".
+ " AND course_id = ". $_SESSION['course_id'];
+ $result = mysql_query($sql, $db) or die(mysql_error());
+ write_to_log(AT_ADMIN_LOG_INSERT, 'basiclti_create', mysql_affected_rows($db), $sql);
+ $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_instructor.php');
+ exit;
+ }
+ }
+}
+
+$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_tools WHERE id = ".$tool.
+ " AND course_id = ". $_SESSION['course_id'];
+$result = mysql_query($sql, $db) or die(mysql_error());
+$toolrow = mysql_fetch_assoc($result);
+if ( $toolrow['id'] != $tool ) {
+ $msg->addFeedback('COULD_NOT_LOAD_TOOL');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_instructor.php');
+ exit;
+}
+
+include(AT_INCLUDE_PATH.'header.inc.php');
+
+$msg->printAll();
+
+?>
+<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" name="basiclti_form" enctype="multipart/form-data">
+ <input type="hidden" name="form_basiclti" value="true" />
+ <input type="hidden" name="id" value="<?php echo $tool; ?>" />
+ <div class="input-form">
+ <fieldset class="group_form"><legend class="group_form"><?php echo _AT('properties'); ?></legend>
+<?php at_form_generate($toolrow, $blti_instructor_form); ?>
+ <div class="buttons">
+ <input type="submit" name="submit" value="<?php echo _AT('save'); ?>" accesskey="s" />
+ <input type="submit" name="cancel" value="<?php echo _AT('cancel');?>" />
+ </div>
+ </fieldset>
+ </div>
+</form>
+
+<?php
+require(AT_INCLUDE_PATH.'footer.inc.php');
--- /dev/null
+<?php
+define('AT_INCLUDE_PATH', '../../../include/');
+require(AT_INCLUDE_PATH.'vitals.inc.php');
+authenticate(AT_PRIV_BASICLTI);
+
+if ( !is_int($_SESSION['course_id']) || $_SESSION['course_id'] < 1 ) {
+ $msg->addFeedback('NEED_COURSE_ID');
+ exit;
+}
+
+require_once('forms.php');
+
+$tool = intval($_REQUEST['id']);
+
+if (isset($_POST['done'])) {
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_instructor.php');
+ exit;
+}
+
+$sql = "SELECT * FROM ".TABLE_PREFIX."basiclti_tools WHERE id = ".$tool.
+ " AND course_id = ". $_SESSION['course_id'];
+$result = mysql_query($sql, $db) or die(mysql_error());
+$toolrow = mysql_fetch_assoc($result);
+if ( $toolrow['id'] != $tool ) {
+ $msg->addFeedback('COULD_NOT_LOAD_TOOL');
+ header('Location: '.AT_BASE_HREF.'mods/basiclti/index_instructor.php');
+ exit;
+}
+
+include(AT_INCLUDE_PATH.'header.inc.php');
+
+$msg->printAll();
+
+?>
+<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" name="basiclti_form" enctype="multipart/form-data">
+ <input type="hidden" name="form_basiclti" value="true" />
+ <input type="hidden" name="id" value="<?php echo $tool; ?>" />
+ <div class="input-form">
+ <fieldset class="group_form"><legend class="group_form"><?php echo _AT('properties'); ?></legend>
+<?php at_form_view($toolrow, $blti_instructor_form); ?>
+ <div class="buttons">
+ <input type="submit" name="done" value="<?php echo _AT('done');?>" />
+ </div>
+ </fieldset>
+ </div>
+</form>
+
+<?php
+require(AT_INCLUDE_PATH.'footer.inc.php');