LTI plugin updates. Finishing support of course level plugins.
authorChris Scribner <scriby@gmail.com>
Mon, 19 Sep 2011 20:26:20 +0000 (16:26 -0400)
committerChris Scribner <scriby@gmail.com>
Mon, 7 Nov 2011 01:41:51 +0000 (20:41 -0500)
mod/lti/ajax.php [new file with mode: 0644]
mod/lti/edit_form.php
mod/lti/instructor_edit_tool_type.php
mod/lti/lang/en/lti.php
mod/lti/locallib.php
mod/lti/mod_form.js
mod/lti/mod_form.php
mod/lti/settings.php

diff --git a/mod/lti/ajax.php b/mod/lti/ajax.php
new file mode 100644 (file)
index 0000000..54eac00
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+require_once(dirname(__FILE__) . "/../../config.php");
+require_once($CFG->dirroot . '/mod/lti/locallib.php');
+
+$courseid = required_param('course', PARAM_INT);
+
+require_login($courseid, false);
+
+$action = required_param('action', PARAM_TEXT);
+
+$response = new stdClass();
+
+switch($action){
+    case 'find_tool_config':
+        $toolurl = required_param('toolurl', PARAM_RAW);
+        
+        $tool = lti_get_tool_by_url_match($toolurl, $courseid);
+        
+        if(!empty($tool)){
+            $response->toolid = $tool->id;
+            $response->toolname = htmlspecialchars($tool->name);
+        }
+        
+        break;
+}
+
+echo json_encode($response);
+
+die;
\ No newline at end of file
index 991f986..cf28c1b 100644 (file)
@@ -84,6 +84,8 @@ class mod_lti_edit_types_form extends moodleform{
             $mform->addElement('hidden', 'lti_coursevisible', '1');
         }
         
+        $mform->addElement('hidden', 'typeid');
+        
         $launchoptions=array();
         $launchoptions[LTI_LAUNCH_CONTAINER_EMBED] = get_string('embed', 'lti');
         $launchoptions[LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS] = get_string('embed_no_blocks', 'lti');
index b32a8a5..7680a48 100644 (file)
@@ -28,18 +28,23 @@ $data = data_submitted();
 if (confirm_sesskey() && isset($data->submitbutton)) {
     $type = new stdClass();
     
-    if (isset($id)) {
-        /*$type->id = $id;
+    if (!empty($typeid)) {
+        $type->id = $typeid;
+        $name = json_encode($data->lti_typename);
 
         lti_update_type($type, $data);
+        
+        //Output script to update the calling window.
         $script = <<<SCRIPT
             <script type="text/javascript">
-                window.opener.M.mod_lti.editor.updateToolType({$name}, '{$id}');
+                window.opener.M.mod_lti.editor.updateToolType({$name}, '{$typeid}');
                 
                 window.close();
             </script>
-SCRIPT;*/
+SCRIPT;
         
+        echo $script;
+                
         die;
     } else {
         $type->state = LTI_TOOL_STATE_CONFIGURED;
@@ -48,6 +53,7 @@ SCRIPT;*/
         $id = lti_add_type($type, $data);
         $name = json_encode($type->name);
         
+        //Output script to update the calling window.
         $script = <<<SCRIPT
             <script type="text/javascript">
                 window.opener.M.mod_lti.editor.addToolType({$name}, '{$id}');
index f8749d8..b949b38 100644 (file)
@@ -50,7 +50,7 @@ $string['acceptgrades'] = 'Accept grades from tool';
 $string['activity'] = 'Activity';
 $string['addnewapp'] = 'Enable External Application';
 $string['addserver'] = 'Add new trusted server';
-$string['addtype'] = 'Add External Tool Configuration';
+$string['addtype'] = 'Add external tool configuration';
 $string['allow'] = 'Allow';
 $string['allowinstructorcustom'] = 'Allow instructors to add custom parameters';
 $string['allowroster'] = 'Tool may access course roster';
@@ -194,3 +194,11 @@ $string['accept_grades'] = 'Accept grades from the tool';
 $string['share_roster'] = 'Allow the tool to access this course\'s roster';
 $string['automatic'] = 'Automatic, based on Launch URL';
 $string['default'] = 'Default';
+
+$string['edittype'] = 'Edit external tool configuration';
+$string['deletetype'] = 'Delete external tool configuration';
+$string['delete_confirmation'] = 'Are you sure you want to delete this external tool configuration?';
+$string['cannot_edit'] = 'You may not edit this tool configuration.';
+$string['cannot_delete'] = 'You may not delete this tool configuration.';
+$string['global_tool_types'] = 'Global tool types';
+$string['course_tool_types'] = 'Course tool types';
\ No newline at end of file
index f1e0731..d2e1c89 100644 (file)
@@ -178,10 +178,6 @@ function lti_build_request($instance, $typeconfig, $course) {
         "launch_presentation_locale" => $locale,
     );
 
-    //$placementsecret = $typeconfig['servicesalt'];
-    
-    //Always use the servicesalt on the instance.
-    //TODO: Remove from type settings
     $placementsecret = $instance->servicesalt;
         
     if ( isset($placementsecret) ) {
@@ -384,10 +380,16 @@ QUERY;
  * Returns all basicLTI tools configured by the administrator
  *
  */
-function lti_filter_get_types() {
+function lti_filter_get_types($course) {
     global $DB;
 
-    return $DB->get_records('lti_types');
+    if(!empty($course)){
+        $filter = array('course' => $course);
+    } else {
+        $filter = array();
+    }
+    
+    return $DB->get_records('lti_types', $filter);
 }
 
 function lti_get_types_for_add_instance(){
@@ -605,6 +607,8 @@ function lti_get_type_type_config($id) {
 
     $type->lti_typename = $basicltitype->name;
     
+    $type->typeid = $basicltitype->id;
+    
     $type->lti_toolurl = $basicltitype->baseurl;
     
     if (isset($config['resourcekey'])) {
@@ -726,6 +730,8 @@ function lti_add_type($type, $config){
     }
     
     //Create a salt value to be used for signing passed data to extension services
+    //The outcome service uses the service salt on the instance. This can be used
+    //for communication with services not related to a specific LTI instance.
     $config->lti_servicesalt = uniqid('', true);
 
     $id = $DB->insert_record('lti_types', $type);
index 5d01de6..79fad16 100644 (file)
-M.mod_lti = M.mod_lti || {};
-
-M.mod_lti.editor = {
-    init: function(Y, settings){
-        this.Y = Y;
-        var self = this;
-        this.settings = Y.JSON.parse(settings);
-
-        var typeSelector = Y.one('#id_typeid');
-        typeSelector.on('change', function(e){
-            self.toggleEditButtons();
-        });
-
-        this.createTypeEditorButtons();
-        
-        this.toggleEditButtons();
-    },
+(function(){
+    var Y;
+    var self;
     
-    getSelectedToolTypeOption: function(){
-        var Y = this.Y;
-        var typeSelector = Y.one('#id_typeid');
-        
-        return typeSelector.one('option[value=' + typeSelector.get('value') + ']');
-    },
-    
-    /**
-     * Adds buttons for creating, editing, and deleting tool types
-     */
-    createTypeEditorButtons: function(){
-        var Y = this.Y;
-        var self = this;
-        
-        var typeSelector = Y.one('#id_typeid');
-        
-        var createIcon = function(id, tooltip, iconUrl){
-            return Y.Node.create('<a />') 
-                    .set('id', id)
-                    .set('title', tooltip)
-                    .setStyle('margin-left', '.5em')
-                    .set('href', 'javascript:void(0);')
-                    .append(Y.Node.create('<img src="' + iconUrl + '" />'));
-        }
-        
-        var addIcon = createIcon('lti_add_tool_type', 'Add new tool type', this.settings.add_icon_url);
-        var editIcon = createIcon('lti_edit_tool_type', 'Edit new tool type', this.settings.edit_icon_url);
-        var deleteIcon  = createIcon('lti_delete_tool_type', 'Delete tool type', this.settings.delete_icon_url);
-        
-        editIcon.on('click', function(e){
-            var toolTypeId = typeSelector.get('value');
-            
-            if(self.getSelectedToolTypeOption().getAttribute('editable')){
-                window.open(self.settings.instructor_tool_type_edit_url + '&action=edit&typeid=' + toolTypeId, 'edit_tool');
+    M.mod_lti = M.mod_lti || {};
+
+    M.mod_lti.editor = {
+        init: function(yui3, settings){
+            if(yui3){
+                Y = yui3;
             }
-        });
-        
-        addIcon.on('click', function(e){
-            window.open(self.settings.instructor_tool_type_edit_url + '&action=add', 'add_tool');
-        });
-        
-        deleteIcon.on('click', function(e){
-            var toolTypeId = typeSelector.get('value');
             
-            if(self.getSelectedToolTypeOption().getAttribute('editable')){
-                Y.io(self.settings.instructor_tool_type_edit_url + '&action=delete&typeid=' + toolTypeId, {
-                    on: {
-                        success: function(){
-                            getSelectedOption().remove();
-                        },
-                        failure: function(){
+            self = this;
+            this.settings = Y.JSON.parse(settings);
+
+            this.urlCache = {};
+
+            this.addOptGroups();
+
+            var typeSelector = Y.one('#id_typeid');
+            typeSelector.on('change', function(e){
+                self.toggleEditButtons();
+            });
+
+            this.createTypeEditorButtons();
+
+            this.toggleEditButtons();
+            
+            var textAreas = new Y.NodeList([
+                Y.one('#id_toolurl'),
+                Y.one('#id_resourcekey'),
+                Y.one('#id_password')
+            ]);
+            
+            var debounce;
+            textAreas.on('keyup', function(e){
+                clearTimeout(debounce);
+
+                //If no more changes within 2 seconds, look up the matching tool URL
+                debounce = setTimeout(function(){
+                    self.updateAutomaticToolMatch();
+                }, 2000);
+            });
+            
+            self.updateAutomaticToolMatch();
+        },
+
+        updateAutomaticToolMatch: function(){
+            var toolurl = Y.one('#id_toolurl');
+            var automatchToolDisplay = Y.one('#lti_automatch_tool');
+
+            if(!automatchToolDisplay){
+                automatchToolDisplay = Y.Node.create('<span />')
+                                        .set('id', 'lti_automatch_tool')
+                                        .setStyle('padding-left', '1em');
+                                        
+                toolurl.insert(automatchToolDisplay, 'after');
+            }
+
+            var url = toolurl.get('value');
 
+            if(!url){
+                automatchToolDisplay.setStyle('display', 'none');
+                return;
+            }
+
+            var key = Y.one('#id_resourcekey');
+            var secret = Y.one('#id_password');
+
+            //We don't care what tool type this tool is associated with if it's manually configured'
+            if(key.get('value') !== '' && secret.get('value') !== ''){
+                automatchToolDisplay.set('innerHTML',  '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />Using custom tool configuration.');
+            } else {
+                var continuation = function(toolInfo){
+                    automatchToolDisplay.setStyle('display', '');
+
+                    if(toolInfo.toolname){
+                        automatchToolDisplay.set('innerHTML',  '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />Using tool configuration: ' + toolInfo.toolname);
+                    } else {
+                        //Inform them custom configuration is in use
+                        if(key.get('value') === '' || secret.get('value') === ''){
+                            automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.yellow_check_icon_url + '" />Tool configuration not found for this URL.');
                         }
                     }
+                };
+                
+                //Cache urls which have already been checked to increaes performance
+                if(self.urlCache[url]){
+                    continuation(self.urlCache[url]);
+                } else {
+                    self.findToolByUrl(url, function(toolInfo){
+                        self.urlCache[url] = toolInfo;
+
+                        continuation(toolInfo);
+                    });
+                }
+            }
+        },
+
+        getSelectedToolTypeOption: function(){
+            var typeSelector = Y.one('#id_typeid');
+
+            return typeSelector.one('option[value=' + typeSelector.get('value') + ']');
+        },
+
+        /**
+         * Separate tool listing into option groups. Server-side select control
+         * doesn't seem to support this.
+         */
+        addOptGroups: function(){
+            var typeSelector = Y.one('#id_typeid');
+
+            if(typeSelector.one('option[courseTool=1]')){
+                //One ore more course tools exist
+
+                var globalGroup = Y.Node.create('<optgroup />')
+                                    .set('id', 'global_tool_group')
+                                    .set('label', M.str.lti.global_tool_types);
+
+                var courseGroup = Y.Node.create('<optgroup />')
+                                    .set('id', 'course_tool_group')
+                                    .set('label', M.str.lti.course_tool_types);
+
+                typeSelector.all('option[globalTool=1]').remove().each(function(node){
+                    globalGroup.append(node);
+                });
+
+                typeSelector.all('option[courseTool=1]').remove().each(function(node){
+                    courseGroup.append(node);
                 });
+
+                typeSelector.append(globalGroup);
+                typeSelector.append(courseGroup);
             }
-        });
-        
-        typeSelector.insert(addIcon, 'after');
-        addIcon.insert(editIcon, 'after');
-        editIcon.insert(deleteIcon, 'after');
-    },
-    
-    toggleEditButtons: function(){
-        var Y = this.Y;
-        
-        var lti_edit_tool_type = Y.one('#lti_edit_tool_type');
-        var lti_delete_tool_type = Y.one('#lti_delete_tool_type');
-        
-        if(this.getSelectedToolTypeOption().getAttribute('editable')){
-            lti_edit_tool_type.setStyle('opacity', '1');
-            lti_delete_tool_type.setStyle('opacity', '1');
-        } else {
-            lti_edit_tool_type.setStyle('opacity', '.2');
-            lti_delete_tool_type.setStyle('opacity', '.2');
-        }
-    },
-    
-    addToolType: function(text, value){
-        var Y = this.Y;
-        var typeSelector = Y.one('#id_typeid');
-        
-        var option = Y.Node.create('<option />')
-                        .set('text', text)
-                        .set('value', value)
-                        .set('selected', 'selected');
-                        
-        typeSelector.append(option);
-    },
-    
-    updateToolType: function(text, value){
-        var Y = this.Y;
-        var typeSelector = Y.one('#id_typeid');
-        
-        var option = Y.Node.create('<option />')
-                        .set('text', text)
-                        .set('value', value)
-                        .set('selected', 'selected');
-                        
-        typeSelector.append(option);
-    }
+        },
+
+        /**
+         * Adds buttons for creating, editing, and deleting tool types.
+         * Javascript is a requirement to edit course level tools at this point.
+         */
+        createTypeEditorButtons: function(){
+            var typeSelector = Y.one('#id_typeid');
+
+            var createIcon = function(id, tooltip, iconUrl){
+                return Y.Node.create('<a />') 
+                        .set('id', id)
+                        .set('title', tooltip)
+                        .setStyle('margin-left', '.5em')
+                        .set('href', 'javascript:void(0);')
+                        .append(Y.Node.create('<img src="' + iconUrl + '" />'));
+            }
+
+            var addIcon = createIcon('lti_add_tool_type', M.str.lti.addtype, this.settings.add_icon_url);
+            var editIcon = createIcon('lti_edit_tool_type', M.str.lti.edittype, this.settings.edit_icon_url);
+            var deleteIcon  = createIcon('lti_delete_tool_type', M.str.lti.deletetype, this.settings.delete_icon_url);
+
+            editIcon.on('click', function(e){
+                var toolTypeId = typeSelector.get('value');
+
+                if(self.getSelectedToolTypeOption().getAttribute('editable')){
+                    window.open(self.settings.instructor_tool_type_edit_url + '&action=edit&typeid=' + toolTypeId, 'edit_tool');
+                } else {
+                    alert(M.str.lti.cannot_edit);
+                }
+            });
+
+            addIcon.on('click', function(e){
+                window.open(self.settings.instructor_tool_type_edit_url + '&action=add', 'add_tool');
+            });
+
+            deleteIcon.on('click', function(e){
+                var toolTypeId = typeSelector.get('value');
+
+                if(self.getSelectedToolTypeOption().getAttribute('editable')){
+                    if(confirm(M.str.lti.delete_confirmation)){
+
+                        Y.io(self.settings.instructor_tool_type_edit_url + '&action=delete&typeid=' + toolTypeId, {
+                            on: {
+                                success: function(){
+                                    self.getSelectedToolTypeOption().remove();
+                                },
+                                failure: function(){
 
-};
+                                }
+                            }
+                        });
+                    }
+                } else {
+                    alert(M.str.lti.cannot_delete);
+                }
+            });
+
+            typeSelector.insert(addIcon, 'after');
+            addIcon.insert(editIcon, 'after');
+            editIcon.insert(deleteIcon, 'after');
+        },
+
+        toggleEditButtons: function(){
+            var lti_edit_tool_type = Y.one('#lti_edit_tool_type');
+            var lti_delete_tool_type = Y.one('#lti_delete_tool_type');
+
+            //Make the edit / delete icons look enabled / disabled.
+            //Does not work in older browsers, but alerts will catch those cases.
+            if(this.getSelectedToolTypeOption().getAttribute('editable')){
+                lti_edit_tool_type.setStyle('opacity', '1');
+                lti_delete_tool_type.setStyle('opacity', '1');
+            } else {
+                lti_edit_tool_type.setStyle('opacity', '.2');
+                lti_delete_tool_type.setStyle('opacity', '.2');
+            }
+        },
+
+        addToolType: function(text, value){
+            var typeSelector = Y.one('#id_typeid');
+            var course_tool_group = Y.one('#course_tool_group');
+
+            var option = Y.Node.create('<option />')
+                            .set('text', text)
+                            .set('value', value)
+                            .set('selected', 'selected')
+                            .setAttribute('editable', '1')
+                            .setAttribute('courseTool', '1');
+
+            if(course_tool_group){
+                course_tool_group.append(option);
+            } else {
+                typeSelector.append(option);
+            }
+        },
+
+        updateToolType: function(text, value){
+            var typeSelector = Y.one('#id_typeid');
+
+            var option = typeSelector.one('option[value=' + value + ']');
+            option.set('text', text);
+        },
+
+        findToolByUrl: function(url, callback){
+            Y.io(self.settings.ajax_url, { 
+                data: { action: 'find_tool_config',
+                        course: self.settings.courseId,
+                        toolurl: url
+                },
 
+                on: {
+                    success: function(transactionid, xhr){
+                        var response = xhr.response;
+                        
+                        var toolInfo = Y.JSON.parse(response);
+                        
+                        callback(toolInfo);
+                    },
+                    failure: function(){
 
+                    }
+                }
+            });
+        }
 
+    };
+})();
\ No newline at end of file
index 3715efc..c9d3714 100644 (file)
@@ -80,7 +80,9 @@ class mod_lti_mod_form extends moodleform_mod {
         
         foreach(lti_get_types_for_add_instance() as $id => $type){
             if($type->course == $COURSE->id) {
-                $attributes = array( 'editable' => 1 );
+                $attributes = array( 'editable' => 1, 'courseTool' => 1 );
+            } else if($id != 0) {
+                $attributes = array( 'globalTool' => 1);
             } else {
                 $attributes = array();
             }
@@ -159,19 +161,34 @@ class mod_lti_mod_form extends moodleform_mod {
         // add standard buttons, common to all modules
         $this->add_action_buttons();
 
-        $url = new moodle_url("/mod/lti/instructor_edit_tool_type.php?sesskey={$USER->sesskey}&course={$COURSE->id}");
+        $editurl = new moodle_url("/mod/lti/instructor_edit_tool_type.php?sesskey={$USER->sesskey}&course={$COURSE->id}");
+        $ajaxurl = new moodle_url('/mod/lti/ajax.php');
+        
         $jsinfo = (object)array(
                         'edit_icon_url' => (string)$OUTPUT->pix_url('t/edit'),
                         'add_icon_url' => (string)$OUTPUT->pix_url('t/add'),
                         'delete_icon_url' => (string)$OUTPUT->pix_url('t/delete'),
-                        'instructor_tool_type_edit_url' => $url->out(false)
+                        'green_check_icon_url' => (string)$OUTPUT->pix_url('i/tick_green_small'),
+                        'yellow_check_icon_url' => (string)$OUTPUT->pix_url('i/tick_amber_small'),
+                        'instructor_tool_type_edit_url' => $editurl->out(false),
+                        'ajax_url' => $ajaxurl->out(true),
+                        'courseId' => $COURSE->id
                   );
         
         $module = array(
             'name'      => 'mod_lti_edit',
             'fullpath'  => '/mod/lti/mod_form.js',
-            'requires'  => array('base', 'io', 'node', 'event', 'json-parse'),
-            'strings'   => array(),
+            'requires'  => array('base', 'io', 'querystring-stringify-simple', 'node', 'event', 'json-parse'),
+            'strings'   => array(
+                array('addtype', 'lti'),
+                array('edittype', 'lti'),
+                array('deletetype', 'lti'),
+                array('delete_confirmation', 'lti'),
+                array('cannot_edit', 'lti'),
+                array('cannot_delete', 'lti'),
+                array('global_tool_types', 'lti'),
+                array('course_tool_types', 'lti')
+            ),
         );
         
         $PAGE->requires->js_init_call('M.mod_lti.editor.init', array(json_encode($jsinfo)), true, $module);
index 3fe1b75..04331e5 100644 (file)
@@ -47,7 +47,7 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-global $PAGE, $CFG;
+global $PAGE, $CFG, $SITE;
 
 require_once($CFG->dirroot.'/mod/lti/locallib.php');
 
@@ -133,8 +133,6 @@ HTML;
 }
 
 if ($ADMIN->fulltree) {
-    require_once($CFG->dirroot.'/mod/lti/locallib.php');
-
     $configuredtoolshtml = '';
     $pendingtoolshtml = '';
     $rejectedtoolshtml = '';
@@ -147,7 +145,7 @@ if ($ADMIN->fulltree) {
     $action = get_string('action', 'lti');
     $createdon = get_string('createdon', 'lti');
     
-    $types = lti_filter_get_types();
+    $types = lti_filter_get_types($SITE->id);
     
     $configuredtools = array_filter($types, function($value){
         return $value->state == LTI_TOOL_STATE_CONFIGURED;