Index: lams_bb_integration/RELEASE_NOTES.TXT =================================================================== diff -u -r02ec3f58f81612b4b45dc150a9f293984068db06 -r46f6e2a2d8c2b6552718747c373df53efd0cfc42 --- lams_bb_integration/RELEASE_NOTES.TXT (.../RELEASE_NOTES.TXT) (revision 02ec3f58f81612b4b45dc150a9f293984068db06) +++ lams_bb_integration/RELEASE_NOTES.TXT (.../RELEASE_NOTES.TXT) (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -123,3 +123,7 @@ * LDEV-3704: Ability to modify BB lessons' URL host name * LDEV-3881: Increase popup size (to 1280x720) * LDEV-3880: Notification ON by default with Blackboard integration + +1.2.19 Release Fixes +==================== +* LDEV-3399: Course Copy feature. Remove auxiliary building block and introduce special button to update LAMS links with new lesson ids. Index: lams_bb_integration/WEB-INF/bb-manifest.xml =================================================================== diff -u -rb98f3e856890754c164c5a4110c7a6cf15ff94a5 -r46f6e2a2d8c2b6552718747c373df53efd0cfc42 --- lams_bb_integration/WEB-INF/bb-manifest.xml (.../bb-manifest.xml) (revision b98f3e856890754c164c5a4110c7a6cf15ff94a5) +++ lams_bb_integration/WEB-INF/bb-manifest.xml (.../bb-manifest.xml) (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -5,7 +5,7 @@ - + @@ -41,6 +41,15 @@ application-def description goes here + + + + + + + + + @@ -57,7 +66,7 @@ - + @@ -73,7 +82,6 @@ - Index: lams_bb_integration/WEB-INF/web.xml =================================================================== diff -u -r9558d30754e0789d7aa74095b325060209da83fc -r46f6e2a2d8c2b6552718747c373df53efd0cfc42 --- lams_bb_integration/WEB-INF/web.xml (.../web.xml) (revision 9558d30754e0789d7aa74095b325060209da83fc) +++ lams_bb_integration/WEB-INF/web.xml (.../web.xml) (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -22,6 +22,10 @@ org.lamsfoundation.ld.integration.blackboard.LamsLearningDesignDeleteServlet + CloneLessonsServlet + org.lamsfoundation.ld.integration.blackboard.CloneLessonsServlet + + GradebookServlet org.lamsfoundation.ld.integration.blackboard.GradebookServlet @@ -55,6 +59,10 @@ /UserData + CloneLessonsServlet + /CloneLessons + + GroupDataServlet /GroupData Index: lams_bb_integration/build.xml =================================================================== diff -u -r0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43 -r46f6e2a2d8c2b6552718747c373df53efd0cfc42 --- lams_bb_integration/build.xml (.../build.xml) (revision 0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43) +++ lams_bb_integration/build.xml (.../build.xml) (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -2,7 +2,7 @@ - + @@ -71,22 +71,4 @@ - - - - - - - - - - - - - - - - - Fisheye: Tag 46f6e2a2d8c2b6552718747c373df53efd0cfc42 refers to a dead (removed) revision in file `lams_bb_integration/conf/lams-course-copy/bb-manifest.xml'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 46f6e2a2d8c2b6552718747c373df53efd0cfc42 refers to a dead (removed) revision in file `lams_bb_integration/conf/lams-course-copy/web.xml'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 46f6e2a2d8c2b6552718747c373df53efd0cfc42 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/cxcomponent/CxComponentImpl.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/CloneLessonsServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/CloneLessonsServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/CloneLessonsServlet.java (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -0,0 +1,239 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.ld.integration.blackboard; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; + +import blackboard.base.BbList; +import blackboard.data.content.Content; +import blackboard.data.course.Course; +import blackboard.data.course.CourseMembership; +import blackboard.data.navigation.CourseToc; +import blackboard.data.user.User; +import blackboard.persist.Id; +import blackboard.persist.PkId; +import blackboard.persist.content.ContentDbLoader; +import blackboard.persist.content.ContentDbPersister; +import blackboard.persist.course.CourseDbLoader; +import blackboard.persist.course.CourseMembershipDbLoader; +import blackboard.persist.navigation.CourseTocDbLoader; +import blackboard.portal.data.ExtraInfo; +import blackboard.portal.data.PortalExtraInfo; +import blackboard.portal.servlet.PortalUtil; +import blackboard.util.StringUtil; + +/** + * Admin on BB side calls this servlet to clone old lesson that were copied to the new course. + */ +public class CloneLessonsServlet extends HttpServlet { + + private static final long serialVersionUID = -3587062723412672084L; + private static Logger logger = Logger.getLogger(CloneLessonsServlet.class); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + String courseIdParam = request.getParameter("courseId"); + if (StringUtil.isEmpty(courseIdParam)) { + throw new RuntimeException("Required parameters are missing. courseId: " + courseIdParam); + } + + String newLessonIds = ""; + try { + CourseDbLoader courseLoader = CourseDbLoader.Default.getInstance(); + Course course = courseLoader.loadByCourseId(courseIdParam); + PkId courseId = (PkId) course.getId(); + String _course_id = "_" + courseId.getPk1() + "_" + courseId.getPk2(); + + // find the main teacher + CourseMembershipDbLoader courseMemLoader = CourseMembershipDbLoader.Default.getInstance(); + BbList monitorCourseMemberships = courseMemLoader.loadByCourseIdAndRole(courseId, + CourseMembership.Role.INSTRUCTOR, null, true); + if (monitorCourseMemberships.isEmpty()) { + BbList teachingAssistantCourseMemberships = courseMemLoader + .loadByCourseIdAndRole(courseId, CourseMembership.Role.TEACHING_ASSISTANT, null, true); + monitorCourseMemberships.addAll(teachingAssistantCourseMemberships); + if (monitorCourseMemberships.isEmpty()) { + BbList courseBuilderCourseMemberships = courseMemLoader + .loadByCourseIdAndRole(courseId, CourseMembership.Role.COURSE_BUILDER, null, true); + monitorCourseMemberships.addAll(courseBuilderCourseMemberships); + } + } + // validate teacher existence + if (monitorCourseMemberships.isEmpty()) { + throw new RuntimeException("There are no monitors in the course courseId=" + courseId); + } + User teacher = monitorCourseMemberships.get(0).getUser(); + + logger.debug("Starting clonning course lessons (courseId=" + courseId + ")."); + + ContentDbLoader contentLoader = ContentDbLoader.Default.getInstance(); + CourseTocDbLoader cTocDbLoader = CourseTocDbLoader.Default.getInstance(); + ContentDbPersister persister =ContentDbPersister.Default.getInstance(); + + //find all lessons that should be updated + + // get a CourseTOC (Table of Contents) loader. We will need this to iterate through all of the "areas" + // within the course + BbList courseTocs = cTocDbLoader.loadByCourseId(courseId); + + // iterate through the course TOC items + for (CourseToc courseToc : courseTocs) { + + // determine if the TOC item is of type "CONTENT" rather than applicaton, or something else + if ((courseToc.getTargetType() == CourseToc.Target.CONTENT) + && (courseToc.getContentId() != Id.UNSET_ID)) { + // we have determined that the TOC item is content, next we need to load the content object and + // iterate through it + // load the content tree into an object "content" and iterate through it + BbList contents = contentLoader.loadListById(courseToc.getContentId()); + // iterate through the content items in this content object + for (Content content : contents) { + // only LAMS content + if ("resource/x-lams-lamscontent".equals(content.getContentHandler())) { + + PkId contentId = (PkId) content.getId(); + String _content_id = "_" + contentId.getPk1() + "_" + contentId.getPk2(); + + String url = content.getUrl(); + String urlLessonId = getParameterValue(url, "lsid"); + String urlCourseId = getParameterValue(url, "course_id"); + String urlContentId = getParameterValue(url, "content_id"); + + //in case when both courseId and contentId don't coincide with the ones from URL - means lesson needs to be cloned + if (!urlCourseId.equals(_course_id) && !urlContentId.equals(_content_id)) { + + final Long newLessonId = LamsSecurityUtil.cloneLesson(teacher, courseIdParam, urlLessonId); + + // update lesson id + content.setLinkRef(Long.toString(newLessonId)); + + // update URL + url = replaceParameterValue(url, "lsid", Long.toString(newLessonId)); + url = replaceParameterValue(url, "course_id", _course_id); + url = replaceParameterValue(url, "content_id", _content_id); + content.setUrl(url); + + // persist updated content + persister.persist(content); + + // store internalContentId -> externalContentId. It's used for lineitem removal (delete.jsp) + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, "LamsStorage"); + ExtraInfo ei = pei.getExtraInfo(); + ei.setValue(_content_id, Long.toString(newLessonId)); + PortalUtil.savePortalExtraInfo(pei); + + // Gradebook column will be copied automatically if appropriate option is selected on + // cloning lesson page + + logger.debug("Lesson (lessonId=" + urlLessonId + + ") was successfully cloned to the one (lessonId=" + newLessonId + ")."); + + newLessonIds += newLessonId + ", "; + } + } + + } + } + } + + } catch (IllegalStateException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (Exception e) { + throw new ServletException(e); + } + + //prepare string to write out + int newLessonsCounts = newLessonIds.length() - newLessonIds.replace(",", "").length(); + String resultStr = "Complete! " + newLessonsCounts + " lessons have been cloned."; + //add all lessonIds (without the last comma) + if (newLessonsCounts > 0) { + resultStr += " Their updated lessonIds: " + newLessonIds.substring(0, newLessonIds.length()-2); + } + logger.debug(resultStr); + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write(resultStr); + out.flush(); + out.close(); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } + + /* + * Returns param value, and empty string in case of there is no such param available + * + * @param url + * @param paramName + * @return + */ + private static String getParameterValue(String url, String paramName) { + String paramValue = ""; + + int quotationMarkIndex = url.indexOf("?"); + String queryPart = quotationMarkIndex > -1 ? url.substring(quotationMarkIndex + 1) : url; + String[] paramEntries = queryPart.split("&"); + for (String paramEntry : paramEntries) { + String[] paramEntrySplitted = paramEntry.split("="); + if ((paramEntrySplitted.length > 1) && paramName.equalsIgnoreCase(paramEntrySplitted[0])) { + paramValue = paramEntrySplitted[1]; + break; + } + } + + return paramValue; + } + + private static String replaceParameterValue(String url, String paramName, String newParamValue) { + String oldParamValue = ""; + + int quotationMarkIndex = url.indexOf("?"); + String queryPart = quotationMarkIndex > -1 ? url.substring(quotationMarkIndex + 1) : url; + String[] paramEntries = queryPart.split("&"); + for (String paramEntry : paramEntries) { + String[] paramEntrySplitted = paramEntry.split("="); + if ((paramEntrySplitted.length > 1) && paramName.equalsIgnoreCase(paramEntrySplitted[0])) { + oldParamValue = paramEntrySplitted[1]; + + return url.replaceFirst(paramName + "=" + oldParamValue, paramName + "=" + newParamValue); + } + } + + return url; + } + +} \ No newline at end of file Index: lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/LamsSecurityUtil.java =================================================================== diff -u -r02ec3f58f81612b4b45dc150a9f293984068db06 -r46f6e2a2d8c2b6552718747c373df53efd0cfc42 --- lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/LamsSecurityUtil.java (.../LamsSecurityUtil.java) (revision 02ec3f58f81612b4b45dc150a9f293984068db06) +++ lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/LamsSecurityUtil.java (.../LamsSecurityUtil.java) (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -41,13 +41,15 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.lamsfoundation.ld.integration.dto.LearnerProgressDTO; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; +import org.xml.sax.SAXException; import blackboard.base.BbList; import blackboard.data.course.CourseMembership; @@ -60,6 +62,7 @@ import blackboard.persist.user.UserDbLoader; import blackboard.platform.BbServiceManager; import blackboard.platform.context.Context; +import blackboard.platform.cx.component.CopyControl; import blackboard.portal.data.ExtraInfo; import blackboard.portal.data.PortalExtraInfo; import blackboard.portal.servlet.PortalUtil; @@ -599,8 +602,7 @@ + country + "&lang=" + lang + "&lsId=" + lsId + "&method=" + method; logger.info("LAMS clone lesson request: " + serviceURL); - - // InputStream is = url.openConnection().getInputStream(); + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); // parse xml response DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -632,8 +634,14 @@ "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", e); } catch (IOException e) { - throw new RuntimeException("Unable to clone LAMS lesson. " + e.getMessage() - + " Please contact your system administrator.", e); + throw new RuntimeException( + "Unable to clone LAMS lesson. " + e.getMessage() + " Please contact your system administrator.", e); + } catch (ParserConfigurationException e) { + throw new RuntimeException( + "Unable to clone LAMS lesson. " + e.getMessage() + " Can't instantiate DocumentBuilder.", e); + } catch (SAXException e) { + throw new RuntimeException( + "Unable to clone LAMS lesson. " + e.getMessage() + " Can't parse LAMS results.", e); } catch (Exception e) { throw new RuntimeException("Unable to clone LAMS lesson. Please contact your system administrator.", e); } Index: lams_bb_integration/web/includes/javascript/jquery.blockUI.js =================================================================== diff -u --- lams_bb_integration/web/includes/javascript/jquery.blockUI.js (revision 0) +++ lams_bb_integration/web/includes/javascript/jquery.blockUI.js (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -0,0 +1,620 @@ +/*! + * jQuery blockUI plugin + * Version 2.70.0-2014.11.23 + * Requires jQuery v1.7 or later + * + * Examples at: http://malsup.com/jquery/block/ + * Copyright (c) 2007-2013 M. Alsup + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Thanks to Amir-Hossein Sobhi for some excellent contributions! + */ + +;(function() { +/*jshint eqeqeq:false curly:false latedef:false */ +"use strict"; + + function setup($) { + $.fn._fadeIn = $.fn.fadeIn; + + var noOp = $.noop || function() {}; + + // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle + // confusing userAgent strings on Vista) + var msie = /MSIE/.test(navigator.userAgent); + var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent); + var mode = document.documentMode || 0; + var setExpr = $.isFunction( document.createElement('div').style.setExpression ); + + // global $ methods for blocking/unblocking the entire page + $.blockUI = function(opts) { install(window, opts); }; + $.unblockUI = function(opts) { remove(window, opts); }; + + // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl) + $.growlUI = function(title, message, timeout, onClose) { + var $m = $('
'); + if (title) $m.append('

'+title+'

'); + if (message) $m.append('

'+message+'

'); + if (timeout === undefined) timeout = 3000; + + // Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications + var callBlock = function(opts) { + opts = opts || {}; + + $.blockUI({ + message: $m, + fadeIn : typeof opts.fadeIn !== 'undefined' ? opts.fadeIn : 700, + fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000, + timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout, + centerY: false, + showOverlay: false, + onUnblock: onClose, + css: $.blockUI.defaults.growlCSS + }); + }; + + callBlock(); + var nonmousedOpacity = $m.css('opacity'); + $m.mouseover(function() { + callBlock({ + fadeIn: 0, + timeout: 30000 + }); + + var displayBlock = $('.blockMsg'); + displayBlock.stop(); // cancel fadeout if it has started + displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency + }).mouseout(function() { + $('.blockMsg').fadeOut(1000); + }); + // End konapun additions + }; + + // plugin method for blocking element content + $.fn.block = function(opts) { + if ( this[0] === window ) { + $.blockUI( opts ); + return this; + } + var fullOpts = $.extend({}, $.blockUI.defaults, opts || {}); + this.each(function() { + var $el = $(this); + if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked')) + return; + $el.unblock({ fadeOut: 0 }); + }); + + return this.each(function() { + if ($.css(this,'position') == 'static') { + this.style.position = 'relative'; + $(this).data('blockUI.static', true); + } + this.style.zoom = 1; // force 'hasLayout' in ie + install(this, opts); + }); + }; + + // plugin method for unblocking element content + $.fn.unblock = function(opts) { + if ( this[0] === window ) { + $.unblockUI( opts ); + return this; + } + return this.each(function() { + remove(this, opts); + }); + }; + + $.blockUI.version = 2.70; // 2nd generation blocking at no extra cost! + + // override these in your code to change the default behavior and style + $.blockUI.defaults = { + // message displayed when blocking (use null for no message) + message: '

Please wait...

', + + title: null, // title string; only used when theme == true + draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) + + theme: false, // set to true to use with jQuery UI themes + + // styles for the message when blocking; if you wish to disable + // these and use an external stylesheet then do this in your code: + // $.blockUI.defaults.css = {}; + css: { + padding: 0, + margin: 0, + width: '30%', + top: '40%', + left: '35%', + textAlign: 'center', + color: '#000', + border: '3px solid #aaa', + backgroundColor:'#fff', + cursor: 'wait' + }, + + // minimal style set used when themes are used + themedCSS: { + width: '30%', + top: '40%', + left: '35%' + }, + + // styles for the overlay + overlayCSS: { + backgroundColor: '#000', + opacity: 0.6, + cursor: 'wait' + }, + + // style to replace wait cursor before unblocking to correct issue + // of lingering wait cursor + cursorReset: 'default', + + // styles applied when using $.growlUI + growlCSS: { + width: '350px', + top: '10px', + left: '', + right: '10px', + border: 'none', + padding: '5px', + opacity: 0.6, + cursor: 'default', + color: '#fff', + backgroundColor: '#000', + '-webkit-border-radius':'10px', + '-moz-border-radius': '10px', + 'border-radius': '10px' + }, + + // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w + // (hat tip to Jorge H. N. de Vasconcelos) + /*jshint scripturl:true */ + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', + + // force usage of iframe in non-IE browsers (handy for blocking applets) + forceIframe: false, + + // z-index for the blocking overlay + baseZ: 1000, + + // set these to true to have the message automatically centered + centerX: true, // <-- only effects element blocking (page block controlled via css above) + centerY: true, + + // allow body element to be stetched in ie6; this makes blocking look better + // on "short" pages. disable if you wish to prevent changes to the body height + allowBodyStretch: true, + + // enable if you want key and mouse events to be disabled for content that is blocked + bindEvents: true, + + // be default blockUI will supress tab navigation from leaving blocking content + // (if bindEvents is true) + constrainTabKey: true, + + // fadeIn time in millis; set to 0 to disable fadeIn on block + fadeIn: 200, + + // fadeOut time in millis; set to 0 to disable fadeOut on unblock + fadeOut: 400, + + // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock + timeout: 0, + + // disable if you don't want to show the overlay + showOverlay: true, + + // if true, focus will be placed in the first available input field when + // page blocking + focusInput: true, + + // elements that can receive focus + focusableElements: ':input:enabled:visible', + + // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) + // no longer needed in 2012 + // applyPlatformOpacityRules: true, + + // callback method invoked when fadeIn has completed and blocking message is visible + onBlock: null, + + // callback method invoked when unblocking has completed; the callback is + // passed the element that has been unblocked (which is the window object for page + // blocks) and the options that were passed to the unblock call: + // onUnblock(element, options) + onUnblock: null, + + // callback method invoked when the overlay area is clicked. + // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used. + onOverlayClick: null, + + // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 + quirksmodeOffsetHack: 4, + + // class name of the message block + blockMsgClass: 'blockMsg', + + // if it is already blocked, then ignore it (don't unblock and reblock) + ignoreIfBlocked: false + }; + + // private data and functions follow... + + var pageBlock = null; + var pageBlockEls = []; + + function install(el, opts) { + var css, themedCSS; + var full = (el == window); + var msg = (opts && opts.message !== undefined ? opts.message : undefined); + opts = $.extend({}, $.blockUI.defaults, opts || {}); + + if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked')) + return; + + opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); + css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); + if (opts.onOverlayClick) + opts.overlayCSS.cursor = 'pointer'; + + themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); + msg = msg === undefined ? opts.message : msg; + + // remove the current block (if there is one) + if (full && pageBlock) + remove(window, {fadeOut:0}); + + // if an existing element is being used as the blocking content then we capture + // its current place in the DOM (and current display style) so we can restore + // it when we unblock + if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { + var node = msg.jquery ? msg[0] : msg; + var data = {}; + $(el).data('blockUI.history', data); + data.el = node; + data.parent = node.parentNode; + data.display = node.style.display; + data.position = node.style.position; + if (data.parent) + data.parent.removeChild(node); + } + + $(el).data('blockUI.onUnblock', opts.onUnblock); + var z = opts.baseZ; + + // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; + // layer1 is the iframe layer which is used to supress bleed through of underlying content + // layer2 is the overlay layer which has opacity and a wait cursor (by default) + // layer3 is the message content that is displayed while blocking + var lyr1, lyr2, lyr3, s; + if (msie || opts.forceIframe) + lyr1 = $(''); + else + lyr1 = $(''); + + if (opts.theme) + lyr2 = $(''); + else + lyr2 = $(''); + + if (opts.theme && full) { + s = ''; + } + else if (opts.theme) { + s = ''; + } + else if (full) { + s = ''; + } + else { + s = ''; + } + lyr3 = $(s); + + // if we have a message, style it + if (msg) { + if (opts.theme) { + lyr3.css(themedCSS); + lyr3.addClass('ui-widget-content'); + } + else + lyr3.css(css); + } + + // style the overlay + if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/) + lyr2.css(opts.overlayCSS); + lyr2.css('position', full ? 'fixed' : 'absolute'); + + // make iframe layer transparent in IE + if (msie || opts.forceIframe) + lyr1.css('opacity',0.0); + + //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); + var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); + $.each(layers, function() { + this.appendTo($par); + }); + + if (opts.theme && opts.draggable && $.fn.draggable) { + lyr3.draggable({ + handle: '.ui-dialog-titlebar', + cancel: 'li' + }); + } + + // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) + var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0); + if (ie6 || expr) { + // give body 100% height + if (full && opts.allowBodyStretch && $.support.boxModel) + $('html,body').css('height','100%'); + + // fix ie6 issue when blocked element has a border width + if ((ie6 || !$.support.boxModel) && !full) { + var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); + var fixT = t ? '(0 - '+t+')' : 0; + var fixL = l ? '(0 - '+l+')' : 0; + } + + // simulate fixed position + $.each(layers, function(i,o) { + var s = o[0].style; + s.position = 'absolute'; + if (i < 2) { + if (full) + s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"'); + else + s.setExpression('height','this.parentNode.offsetHeight + "px"'); + if (full) + s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'); + else + s.setExpression('width','this.parentNode.offsetWidth + "px"'); + if (fixL) s.setExpression('left', fixL); + if (fixT) s.setExpression('top', fixT); + } + else if (opts.centerY) { + if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); + s.marginTop = 0; + } + else if (!opts.centerY && full) { + var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0; + var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; + s.setExpression('top',expression); + } + }); + } + + // show the message + if (msg) { + if (opts.theme) + lyr3.find('.ui-widget-content').append(msg); + else + lyr3.append(msg); + if (msg.jquery || msg.nodeType) + $(msg).show(); + } + + if ((msie || opts.forceIframe) && opts.showOverlay) + lyr1.show(); // opacity is zero + if (opts.fadeIn) { + var cb = opts.onBlock ? opts.onBlock : noOp; + var cb1 = (opts.showOverlay && !msg) ? cb : noOp; + var cb2 = msg ? cb : noOp; + if (opts.showOverlay) + lyr2._fadeIn(opts.fadeIn, cb1); + if (msg) + lyr3._fadeIn(opts.fadeIn, cb2); + } + else { + if (opts.showOverlay) + lyr2.show(); + if (msg) + lyr3.show(); + if (opts.onBlock) + opts.onBlock.bind(lyr3)(); + } + + // bind key and mouse events + bind(1, el, opts); + + if (full) { + pageBlock = lyr3[0]; + pageBlockEls = $(opts.focusableElements,pageBlock); + if (opts.focusInput) + setTimeout(focus, 20); + } + else + center(lyr3[0], opts.centerX, opts.centerY); + + if (opts.timeout) { + // auto-unblock + var to = setTimeout(function() { + if (full) + $.unblockUI(opts); + else + $(el).unblock(opts); + }, opts.timeout); + $(el).data('blockUI.timeout', to); + } + } + + // remove the block + function remove(el, opts) { + var count; + var full = (el == window); + var $el = $(el); + var data = $el.data('blockUI.history'); + var to = $el.data('blockUI.timeout'); + if (to) { + clearTimeout(to); + $el.removeData('blockUI.timeout'); + } + opts = $.extend({}, $.blockUI.defaults, opts || {}); + bind(0, el, opts); // unbind events + + if (opts.onUnblock === null) { + opts.onUnblock = $el.data('blockUI.onUnblock'); + $el.removeData('blockUI.onUnblock'); + } + + var els; + if (full) // crazy selector to handle odd field errors in ie6/7 + els = $('body').children().filter('.blockUI').add('body > .blockUI'); + else + els = $el.find('>.blockUI'); + + // fix cursor issue + if ( opts.cursorReset ) { + if ( els.length > 1 ) + els[1].style.cursor = opts.cursorReset; + if ( els.length > 2 ) + els[2].style.cursor = opts.cursorReset; + } + + if (full) + pageBlock = pageBlockEls = null; + + if (opts.fadeOut) { + count = els.length; + els.stop().fadeOut(opts.fadeOut, function() { + if ( --count === 0) + reset(els,data,opts,el); + }); + } + else + reset(els, data, opts, el); + } + + // move blocking element back into the DOM where it started + function reset(els,data,opts,el) { + var $el = $(el); + if ( $el.data('blockUI.isBlocked') ) + return; + + els.each(function(i,o) { + // remove via DOM calls so we don't lose event handlers + if (this.parentNode) + this.parentNode.removeChild(this); + }); + + if (data && data.el) { + data.el.style.display = data.display; + data.el.style.position = data.position; + data.el.style.cursor = 'default'; // #59 + if (data.parent) + data.parent.appendChild(data.el); + $el.removeData('blockUI.history'); + } + + if ($el.data('blockUI.static')) { + $el.css('position', 'static'); // #22 + } + + if (typeof opts.onUnblock == 'function') + opts.onUnblock(el,opts); + + // fix issue in Safari 6 where block artifacts remain until reflow + var body = $(document.body), w = body.width(), cssW = body[0].style.width; + body.width(w-1).width(w); + body[0].style.width = cssW; + } + + // bind/unbind the handler + function bind(b, el, opts) { + var full = el == window, $el = $(el); + + // don't bother unbinding if there is nothing to unbind + if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) + return; + + $el.data('blockUI.isBlocked', b); + + // don't bind events when overlay is not in use or if bindEvents is false + if (!full || !opts.bindEvents || (b && !opts.showOverlay)) + return; + + // bind anchors and inputs for mouse and key events + var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove'; + if (b) + $(document).bind(events, opts, handler); + else + $(document).unbind(events, handler); + + // former impl... + // var $e = $('a,:input'); + // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); + } + + // event handler to suppress keyboard/mouse events when blocking + function handler(e) { + // allow tab navigation (conditionally) + if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) { + if (pageBlock && e.data.constrainTabKey) { + var els = pageBlockEls; + var fwd = !e.shiftKey && e.target === els[els.length-1]; + var back = e.shiftKey && e.target === els[0]; + if (fwd || back) { + setTimeout(function(){focus(back);},10); + return false; + } + } + } + var opts = e.data; + var target = $(e.target); + if (target.hasClass('blockOverlay') && opts.onOverlayClick) + opts.onOverlayClick(e); + + // allow events within the message content + if (target.parents('div.' + opts.blockMsgClass).length > 0) + return true; + + // allow events for content that is not being blocked + return target.parents().children().filter('div.blockUI').length === 0; + } + + function focus(back) { + if (!pageBlockEls) + return; + var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; + if (e) + e.focus(); + } + + function center(el, x, y) { + var p = el.parentNode, s = el.style; + var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); + var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); + if (x) s.left = l > 0 ? (l+'px') : '0'; + if (y) s.top = t > 0 ? (t+'px') : '0'; + } + + function sz(el, p) { + return parseInt($.css(el,p),10)||0; + } + + } + + + /*global define:true */ + if (typeof define === 'function' && define.amd && define.amd.jQuery) { + define(['jquery'], setup); + } else { + setup(jQuery); + } + +})(); Index: lams_bb_integration/web/links/admin.jsp =================================================================== diff -u --- lams_bb_integration/web/links/admin.jsp (revision 0) +++ lams_bb_integration/web/links/admin.jsp (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -0,0 +1,105 @@ +<%@ page import="java.util.*, java.net.*, + java.text.SimpleDateFormat, + blackboard.data.*, + blackboard.persist.*, + blackboard.data.course.*, + blackboard.data.user.*, + blackboard.data.navigation.*, + blackboard.persist.course.*, + blackboard.persist.navigation.*, + blackboard.data.content.*, + blackboard.persist.content.*, + blackboard.db.*, + blackboard.base.*, + blackboard.platform.*, + blackboard.platform.plugin.*, + org.lamsfoundation.ld.integration.*, + org.lamsfoundation.ld.integration.blackboard.*" errorPage="error.jsp" %> +<%@ taglib uri="/bbNG" prefix="bbNG"%> +<%@ taglib uri="/bbData" prefix="bbData"%> + + +<% + //check permission + if (!PlugInUtil.authorizeForCourseControlPanel(request, response)) { + return; + } +%> + + + + + + + + + + LAMS Admin + + + + + + <%-- Monitor Button --%> +
+ +
+ +
+ + + + + +
+
\ No newline at end of file Index: lams_bb_integration/web/modules/learnermonitor.jsp =================================================================== diff -u -r2f7946263b1051bbc483157e2992e1403ee330df -r46f6e2a2d8c2b6552718747c373df53efd0cfc42 --- lams_bb_integration/web/modules/learnermonitor.jsp (.../learnermonitor.jsp) (revision 2f7946263b1051bbc483157e2992e1403ee330df) +++ lams_bb_integration/web/modules/learnermonitor.jsp (.../learnermonitor.jsp) (revision 46f6e2a2d8c2b6552718747c373df53efd0cfc42) @@ -78,16 +78,16 @@ // Get Course ID and Session User ID BbPersistenceManager bbPm = BbServiceManager.getPersistenceService().getDbPersistenceManager(); - String course_idstr = request.getParameter("course_id"); - Id course_id = bbPm.generateId(Course.DATA_TYPE, course_idstr); + String courseIdParam = request.getParameter("course_id"); + Id course_id = bbPm.generateId(Course.DATA_TYPE, courseIdParam); User sessionUser = ctx.getUser(); Id sessionUserId = sessionUser.getId(); // Get the membership data to determine the User's Role CourseMembership courseMembership = null; CourseMembership.Role courseRole = null; boolean isActive = false; - + CourseMembershipDbLoader sessionCourseMembershipLoader = (CourseMembershipDbLoader) bbPm.getLoader(CourseMembershipDbLoader.TYPE); try { courseMembership = sessionCourseMembershipLoader.loadByCourseAndUserId(course_id, sessionUserId); @@ -104,7 +104,7 @@ // Is the User an Instructor of Teaching Assistant boolean instructorstr=false; if (CourseMembership.Role.INSTRUCTOR.equals(courseRole) || CourseMembership.Role.TEACHING_ASSISTANT.equals(courseRole) - || CourseMembership.Role.COURSE_BUILDER.equals(courseRole)) { + || CourseMembership.Role.COURSE_BUILDER.equals(courseRole)) { instructorstr=true; } else if (!CourseMembership.Role.STUDENT.equals(courseRole)) { @@ -118,72 +118,74 @@ } //Get lessson's title and description - String contentIdStr = request.getParameter("content_id"); String title = ""; String description = ""; String strLineitemId = null; String position = "unknown"; //contentId is available in versions after 1.2.3 - if (contentIdStr != null) { + String contentIdParam = request.getParameter("content_id"); + if (contentIdParam != null) { Container bbContainer = bbPm.getContainer(); - Id contentId = new PkId( bbContainer, CourseDocument.DATA_TYPE, request.getParameter("content_id") ); - ContentDbLoader courseDocumentLoader = (ContentDbLoader) bbPm.getLoader( ContentDbLoader.TYPE ); - Content courseDoc = (Content)courseDocumentLoader.loadById( contentId ); + Id contentId = new PkId(bbContainer, CourseDocument.DATA_TYPE, contentIdParam); + ContentDbLoader courseDocumentLoader = (ContentDbLoader) bbPm.getLoader(ContentDbLoader.TYPE); + Content courseDoc = (Content) courseDocumentLoader.loadById(contentId); title = courseDoc.getTitle(); description = courseDoc.getBody().getFormattedText(); position = String.valueOf(courseDoc.getPosition()); //get lineitemid from the storage (bbContentId -> lineitemid) PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, "LamsLineitemStorage"); ExtraInfo ei = pei.getExtraInfo(); - strLineitemId = ei.getValue(contentIdStr); + strLineitemId = ei.getValue(contentIdParam); } else { - + title = (request.getParameter("title") != null) ? request.getParameter("title") : "LAMS Options"; description = request.getParameter("description"); strLineitemId = request.getParameter("lineitemid"); } //display learning design image - String strIsDisplayDesignImage = request.getParameter("isDisplayDesignImage"); - boolean isDisplayDesignImage = "true".equals(strIsDisplayDesignImage)?true:false; - String learningDesignImageUrl = ""; - if (isDisplayDesignImage) { - String strLearningDesignId = request.getParameter("ldid").trim(); - long learningDesignId = Long.parseLong(strLearningDesignId); - - learningDesignImageUrl = LamsSecurityUtil.generateRequestLearningDesignImage(ctx, false) + "&ldId=" + learningDesignId; - } - - //check whether user has score for this lesson - Score current_score = null; - if (strLineitemId != null) { // there won't be "lineitemid" parameter in case lesson had been created in LAMS building block version prior to 1.2 - - //check if it was created in old versions (e.g. 1.2.1) where lineitemId parameter had the following format "PkId(key=_XXXX_1, %20dataType=blackboard.data.gradebook.impl.OutcomeDefinition, %20container=blackboard.persist.DatabaseContainer@xxxx)" - //in which case transform it to the more regular one - if (strLineitemId.contains("key=")) { - int pos1 = strLineitemId.indexOf("key=") + 4; - int pos2 = strLineitemId.indexOf(",", pos1); + String strIsDisplayDesignImage = request.getParameter("isDisplayDesignImage"); + boolean isDisplayDesignImage = "true".equals(strIsDisplayDesignImage) ? true : false; + String learningDesignImageUrl = ""; + if (isDisplayDesignImage) { + String strLearningDesignId = request.getParameter("ldid").trim(); + long learningDesignId = Long.parseLong(strLearningDesignId); - if (pos1 != -1 && pos2 != -1 && pos1 <= pos2 && pos2 <= strLineitemId.length()) { - strLineitemId = strLineitemId.substring(pos1, pos2); - } - } - + learningDesignImageUrl = LamsSecurityUtil.generateRequestLearningDesignImage(ctx, false) + "&ldId=" + + learningDesignId; + } + + //check whether user has score for this lesson + Score current_score = null; + if (strLineitemId != null) { // there won't be "lineitemid" parameter in case lesson had been created in LAMS building block version prior to 1.2 + + //check if it was created in old versions (e.g. 1.2.1) where lineitemId parameter had the following format "PkId(key=_XXXX_1, %20dataType=blackboard.data.gradebook.impl.OutcomeDefinition, %20container=blackboard.persist.DatabaseContainer@xxxx)" + //in which case transform it to the more regular one + if (strLineitemId.contains("key=")) { + int pos1 = strLineitemId.indexOf("key=") + 4; + int pos2 = strLineitemId.indexOf(",", pos1); + + if (pos1 != -1 && pos2 != -1 && pos1 <= pos2 && pos2 <= strLineitemId.length()) { + strLineitemId = strLineitemId.substring(pos1, pos2); + } + } + Id lineitemId = bbPm.generateId(Lineitem.LINEITEM_DATA_TYPE, strLineitemId.trim()); ScoreDbLoader scoreLoader = (ScoreDbLoader) bbPm.getLoader(ScoreDbLoader.TYPE); - try { - current_score = scoreLoader.loadByCourseMembershipIdAndLineitemId(courseMembership.getId(), lineitemId); - } catch (KeyNotFoundException c) { - //no score availalbe - } + try { + current_score = scoreLoader.loadByCourseMembershipIdAndLineitemId(courseMembership.getId(), + lineitemId); + } catch (KeyNotFoundException c) { + //no score availalbe + } } boolean isScoreAvailable = (current_score != null); String strLessonId = request.getParameter("lsid").trim(); - long lessonId = Long.parseLong(strLessonId); + long lessonId = Long.parseLong(strLessonId); LearnerProgressDTO learnerProgressDto = LamsSecurityUtil.getLearnerProgress(ctx, lessonId); %>