moved basiclti mod from the basiclti ATutor branch into trunk/mods/
authorgreg gay <ggay@ocad.ca>
Thu, 27 Jan 2011 18:30:57 +0000 (18:30 -0000)
committergreg gay <ggay@ocad.ca>
Thu, 27 Jan 2011 18:30:57 +0000 (18:30 -0000)
45 files changed:
mods/basiclti/ModuleCallbacks.class.php [new file with mode: 0644]
mods/basiclti/README.txt [new file with mode: 0644]
mods/basiclti/TODO.txt [new file with mode: 0644]
mods/basiclti/atutor-patches-01.txt [new file with mode: 0644]
mods/basiclti/atutor-patches-02.txt [new file with mode: 0644]
mods/basiclti/basiclti.jpg [new file with mode: 0644]
mods/basiclti/content_tool_action.js [new file with mode: 0644]
mods/basiclti/images/basiclti-icon.png [new file with mode: 0644]
mods/basiclti/include/constants.inc.php [new file with mode: 0644]
mods/basiclti/index.php [new file with mode: 0644]
mods/basiclti/index_admin.php [new file with mode: 0644]
mods/basiclti/index_instructor.php [new file with mode: 0644]
mods/basiclti/index_mystart.php [new file with mode: 0644]
mods/basiclti/index_public.php [new file with mode: 0644]
mods/basiclti/launch/TrivialStore.php [new file with mode: 0644]
mods/basiclti/launch/ims-blti/LICENSE.txt [new file with mode: 0644]
mods/basiclti/launch/ims-blti/OAuth.php [new file with mode: 0644]
mods/basiclti/launch/ims-blti/blti.php [new file with mode: 0644]
mods/basiclti/launch/ims-blti/blti_util.php [new file with mode: 0644]
mods/basiclti/launch/launch.php [new file with mode: 0644]
mods/basiclti/launch/loadrows.php [new file with mode: 0644]
mods/basiclti/launch/service.php [new file with mode: 0644]
mods/basiclti/lib/at_form_util.php [new file with mode: 0644]
mods/basiclti/module.css [new file with mode: 0644]
mods/basiclti/module.php [new file with mode: 0644]
mods/basiclti/module.sql [new file with mode: 0644]
mods/basiclti/module.xml [new file with mode: 0644]
mods/basiclti/module_backup.php [new file with mode: 0644]
mods/basiclti/module_cron.php [new file with mode: 0644]
mods/basiclti/module_delete.php [new file with mode: 0644]
mods/basiclti/module_format_content.php [new file with mode: 0644]
mods/basiclti/module_install.php [new file with mode: 0644]
mods/basiclti/module_uninstall.php [new file with mode: 0644]
mods/basiclti/side_menu.inc.php [new file with mode: 0644]
mods/basiclti/sublinks.php [new file with mode: 0644]
mods/basiclti/tool/admin_create.php [new file with mode: 0644]
mods/basiclti/tool/admin_delete.php [new file with mode: 0644]
mods/basiclti/tool/admin_edit.php [new file with mode: 0644]
mods/basiclti/tool/admin_view.php [new file with mode: 0644]
mods/basiclti/tool/content_edit.php [new file with mode: 0644]
mods/basiclti/tool/forms.php [new file with mode: 0644]
mods/basiclti/tool/instructor_create.php [new file with mode: 0644]
mods/basiclti/tool/instructor_delete.php [new file with mode: 0644]
mods/basiclti/tool/instructor_edit.php [new file with mode: 0644]
mods/basiclti/tool/instructor_view.php [new file with mode: 0644]

diff --git a/mods/basiclti/ModuleCallbacks.class.php b/mods/basiclti/ModuleCallbacks.class.php
new file mode 100644 (file)
index 0000000..6f60ab9
--- /dev/null
@@ -0,0 +1,59 @@
+<?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";
+               }
+       }
+
+}
+
+?>
diff --git a/mods/basiclti/README.txt b/mods/basiclti/README.txt
new file mode 100644 (file)
index 0000000..9219436
--- /dev/null
@@ -0,0 +1,34 @@
+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
+
diff --git a/mods/basiclti/TODO.txt b/mods/basiclti/TODO.txt
new file mode 100644 (file)
index 0000000..17a191f
--- /dev/null
@@ -0,0 +1,13 @@
+
+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.
+
+
diff --git a/mods/basiclti/atutor-patches-01.txt b/mods/basiclti/atutor-patches-01.txt
new file mode 100644 (file)
index 0000000..0e101a2
--- /dev/null
@@ -0,0 +1,131 @@
+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');
diff --git a/mods/basiclti/atutor-patches-02.txt b/mods/basiclti/atutor-patches-02.txt
new file mode 100644 (file)
index 0000000..33db2ec
--- /dev/null
@@ -0,0 +1,49 @@
+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
++?>
diff --git a/mods/basiclti/basiclti.jpg b/mods/basiclti/basiclti.jpg
new file mode 100644 (file)
index 0000000..c6cf71b
Binary files /dev/null and b/mods/basiclti/basiclti.jpg differ
diff --git a/mods/basiclti/content_tool_action.js b/mods/basiclti/content_tool_action.js
new file mode 100644 (file)
index 0000000..bdf5ed4
--- /dev/null
@@ -0,0 +1,29 @@
+/* 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);
+})();
diff --git a/mods/basiclti/images/basiclti-icon.png b/mods/basiclti/images/basiclti-icon.png
new file mode 100644 (file)
index 0000000..7333047
Binary files /dev/null and b/mods/basiclti/images/basiclti-icon.png differ
diff --git a/mods/basiclti/include/constants.inc.php b/mods/basiclti/include/constants.inc.php
new file mode 100644 (file)
index 0000000..dcdd0a7
--- /dev/null
@@ -0,0 +1,7 @@
+<?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/');
+?>
diff --git a/mods/basiclti/index.php b/mods/basiclti/index.php
new file mode 100644 (file)
index 0000000..7b9edcc
--- /dev/null
@@ -0,0 +1,12 @@
+<?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'); ?>
diff --git a/mods/basiclti/index_admin.php b/mods/basiclti/index_admin.php
new file mode 100644 (file)
index 0000000..ec8684a
--- /dev/null
@@ -0,0 +1,56 @@
+<?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>&nbsp;</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');
+?>
diff --git a/mods/basiclti/index_instructor.php b/mods/basiclti/index_instructor.php
new file mode 100644 (file)
index 0000000..f5385a0
--- /dev/null
@@ -0,0 +1,61 @@
+<?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>&nbsp;</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');
+?>
diff --git a/mods/basiclti/index_mystart.php b/mods/basiclti/index_mystart.php
new file mode 100644 (file)
index 0000000..70d97b7
--- /dev/null
@@ -0,0 +1,13 @@
+<?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'); ?>
diff --git a/mods/basiclti/index_public.php b/mods/basiclti/index_public.php
new file mode 100644 (file)
index 0000000..0666705
--- /dev/null
@@ -0,0 +1,15 @@
+<?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'); ?>
diff --git a/mods/basiclti/launch/TrivialStore.php b/mods/basiclti/launch/TrivialStore.php
new file mode 100644 (file)
index 0000000..5060577
--- /dev/null
@@ -0,0 +1,45 @@
+<?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;
+    }
+}
+?>
diff --git a/mods/basiclti/launch/ims-blti/LICENSE.txt b/mods/basiclti/launch/ims-blti/LICENSE.txt
new file mode 100644 (file)
index 0000000..89f0591
--- /dev/null
@@ -0,0 +1,22 @@
+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.
+
diff --git a/mods/basiclti/launch/ims-blti/OAuth.php b/mods/basiclti/launch/ims-blti/OAuth.php
new file mode 100644 (file)
index 0000000..226d6d4
--- /dev/null
@@ -0,0 +1,808 @@
+<?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);
+  }
+}
+
+?>
diff --git a/mods/basiclti/launch/ims-blti/blti.php b/mods/basiclti/launch/ims-blti/blti.php
new file mode 100644 (file)
index 0000000..f08bbf3
--- /dev/null
@@ -0,0 +1,324 @@
+<?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;
+    }
+
+}
+
+?>
diff --git a/mods/basiclti/launch/ims-blti/blti_util.php b/mods/basiclti/launch/ims-blti/blti_util.php
new file mode 100644 (file)
index 0000000..eb67c99
--- /dev/null
@@ -0,0 +1,225 @@
+<?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&nbsp;<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 .= "&nbsp;<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;
+}
+
diff --git a/mods/basiclti/launch/launch.php b/mods/basiclti/launch/launch.php
new file mode 100644 (file)
index 0000000..9bc5790
--- /dev/null
@@ -0,0 +1,120 @@
+<?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);
+
+
+?>
diff --git a/mods/basiclti/launch/loadrows.php b/mods/basiclti/launch/loadrows.php
new file mode 100644 (file)
index 0000000..2ddc05b
--- /dev/null
@@ -0,0 +1,65 @@
+<?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");
+
+?>
diff --git a/mods/basiclti/launch/service.php b/mods/basiclti/launch/service.php
new file mode 100644 (file)
index 0000000..1d59f14
--- /dev/null
@@ -0,0 +1,336 @@
+<?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);
+
+    }
+    
+?>
diff --git a/mods/basiclti/lib/at_form_util.php b/mods/basiclti/lib/at_form_util.php
new file mode 100644 (file)
index 0000000..cdab05b
--- /dev/null
@@ -0,0 +1,333 @@
+<?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);
+
+}
+
diff --git a/mods/basiclti/module.css b/mods/basiclti/module.css
new file mode 100644 (file)
index 0000000..15f51ae
--- /dev/null
@@ -0,0 +1,11 @@
+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
diff --git a/mods/basiclti/module.php b/mods/basiclti/module.php
new file mode 100644 (file)
index 0000000..b21bf64
--- /dev/null
@@ -0,0 +1,119 @@
+<?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';
+}
+
+
+?>
diff --git a/mods/basiclti/module.sql b/mods/basiclti/module.sql
new file mode 100644 (file)
index 0000000..e7b2d12
--- /dev/null
@@ -0,0 +1,126 @@
+# 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
diff --git a/mods/basiclti/module.xml b/mods/basiclti/module.xml
new file mode 100644 (file)
index 0000000..9173e5b
--- /dev/null
@@ -0,0 +1,19 @@
+<?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>
diff --git a/mods/basiclti/module_backup.php b/mods/basiclti/module_backup.php
new file mode 100644 (file)
index 0000000..14afc29
--- /dev/null
@@ -0,0 +1,18 @@
+<?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;
+}
+
+?>
diff --git a/mods/basiclti/module_cron.php b/mods/basiclti/module_cron.php
new file mode 100644 (file)
index 0000000..2d4f74d
--- /dev/null
@@ -0,0 +1,13 @@
+<?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!');
+}
+
+?>
diff --git a/mods/basiclti/module_delete.php b/mods/basiclti/module_delete.php
new file mode 100644 (file)
index 0000000..a169a82
--- /dev/null
@@ -0,0 +1,21 @@
+<?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);
+}
+
+?>
diff --git a/mods/basiclti/module_format_content.php b/mods/basiclti/module_format_content.php
new file mode 100644 (file)
index 0000000..eb6d3f0
--- /dev/null
@@ -0,0 +1,21 @@
+<?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
diff --git a/mods/basiclti/module_install.php b/mods/basiclti/module_install.php
new file mode 100644 (file)
index 0000000..cec25ef
--- /dev/null
@@ -0,0 +1,65 @@
+<?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);
+}
+
+?>
diff --git a/mods/basiclti/module_uninstall.php b/mods/basiclti/module_uninstall.php
new file mode 100644 (file)
index 0000000..6e6a507
--- /dev/null
@@ -0,0 +1,45 @@
+<?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);
+}
+
+?>
+
diff --git a/mods/basiclti/side_menu.inc.php b/mods/basiclti/side_menu.inc.php
new file mode 100644 (file)
index 0000000..871e505
--- /dev/null
@@ -0,0 +1,13 @@
+<?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');
+?>
diff --git a/mods/basiclti/sublinks.php b/mods/basiclti/sublinks.php
new file mode 100644 (file)
index 0000000..bf602a3
--- /dev/null
@@ -0,0 +1,34 @@
+<?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;
+}
+
+?>
diff --git a/mods/basiclti/tool/admin_create.php b/mods/basiclti/tool/admin_create.php
new file mode 100644 (file)
index 0000000..4686a79
--- /dev/null
@@ -0,0 +1,53 @@
+<?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');
diff --git a/mods/basiclti/tool/admin_delete.php b/mods/basiclti/tool/admin_delete.php
new file mode 100644 (file)
index 0000000..157922d
--- /dev/null
@@ -0,0 +1,48 @@
+<?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'); 
diff --git a/mods/basiclti/tool/admin_edit.php b/mods/basiclti/tool/admin_edit.php
new file mode 100644 (file)
index 0000000..d2f2f13
--- /dev/null
@@ -0,0 +1,65 @@
+<?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');
diff --git a/mods/basiclti/tool/admin_view.php b/mods/basiclti/tool/admin_view.php
new file mode 100644 (file)
index 0000000..2b823a2
--- /dev/null
@@ -0,0 +1,43 @@
+<?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');
diff --git a/mods/basiclti/tool/content_edit.php b/mods/basiclti/tool/content_edit.php
new file mode 100644 (file)
index 0000000..f2da377
--- /dev/null
@@ -0,0 +1,186 @@
+<?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--">&nbsp;</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--">&nbsp;</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'); ?>
diff --git a/mods/basiclti/tool/forms.php b/mods/basiclti/tool/forms.php
new file mode 100644 (file)
index 0000000..4110621
--- /dev/null
@@ -0,0 +1,88 @@
+<?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");
+    }
+}
+?>
diff --git a/mods/basiclti/tool/instructor_create.php b/mods/basiclti/tool/instructor_create.php
new file mode 100644 (file)
index 0000000..beb9e8f
--- /dev/null
@@ -0,0 +1,59 @@
+<?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');
diff --git a/mods/basiclti/tool/instructor_delete.php b/mods/basiclti/tool/instructor_delete.php
new file mode 100644 (file)
index 0000000..66f7ebd
--- /dev/null
@@ -0,0 +1,52 @@
+<?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'); 
diff --git a/mods/basiclti/tool/instructor_edit.php b/mods/basiclti/tool/instructor_edit.php
new file mode 100644 (file)
index 0000000..cd8b3d7
--- /dev/null
@@ -0,0 +1,74 @@
+<?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');
diff --git a/mods/basiclti/tool/instructor_view.php b/mods/basiclti/tool/instructor_view.php
new file mode 100644 (file)
index 0000000..e2186bd
--- /dev/null
@@ -0,0 +1,49 @@
+<?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');