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
+
+ GradebookServletorg.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 = '
';
+ if ( opts.title ) {
+ s += '
'+(opts.title || ' ')+'
';
+ }
+ s += '';
+ s += '
';
+ }
+ else if (opts.theme) {
+ s = '
';
+ if ( opts.title ) {
+ s += '
'+(opts.title || ' ')+'
';
+ }
+ s += '';
+ 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);
%>