Index: lams_bb_integration/RELEASE_NOTES.TXT
===================================================================
diff -u -rd27ed028b0e16c263776418b7bce22099fed4eed -r0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43
--- lams_bb_integration/RELEASE_NOTES.TXT (.../RELEASE_NOTES.TXT) (revision d27ed028b0e16c263776418b7bce22099fed4eed)
+++ lams_bb_integration/RELEASE_NOTES.TXT (.../RELEASE_NOTES.TXT) (revision 0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43)
@@ -115,4 +115,8 @@
* LDEV-3510: Previous releases were opening lessons in a new window due to a change made for LDEV-3510.
Now reverted back to previous behaviour with lesson loading in the same tab.
* LKC-61: Gradebook syncing change
-* LDEV-3621: Ability to import and use groups from integrated server
\ No newline at end of file
+* LDEV-3621: Ability to import and use groups from integrated server
+
+1.2.18 Release Fixes
+====================
+* LDEV-3399: Support Course Copy feature in Blackboard
\ No newline at end of file
Index: lams_bb_integration/WEB-INF/bb-manifest.xml
===================================================================
diff -u -r7d945ecdc0463c75a58f16251e203068bb2e8149 -r0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43
--- lams_bb_integration/WEB-INF/bb-manifest.xml (.../bb-manifest.xml) (revision 7d945ecdc0463c75a58f16251e203068bb2e8149)
+++ lams_bb_integration/WEB-INF/bb-manifest.xml (.../bb-manifest.xml) (revision 0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43)
@@ -5,7 +5,7 @@
-
+
@@ -79,6 +79,7 @@
+
Index: lams_bb_integration/build.xml
===================================================================
diff -u -r7d945ecdc0463c75a58f16251e203068bb2e8149 -r0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43
--- lams_bb_integration/build.xml (.../build.xml) (revision 7d945ecdc0463c75a58f16251e203068bb2e8149)
+++ lams_bb_integration/build.xml (.../build.xml) (revision 0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43)
@@ -2,7 +2,7 @@
-
+
@@ -71,4 +71,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: lams_bb_integration/conf/lams-course-copy/bb-manifest.xml
===================================================================
diff -u
--- lams_bb_integration/conf/lams-course-copy/bb-manifest.xml (revision 0)
+++ lams_bb_integration/conf/lams-course-copy/bb-manifest.xml (revision 0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43)
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: lams_bb_integration/conf/lams-course-copy/web.xml
===================================================================
diff -u
--- lams_bb_integration/conf/lams-course-copy/web.xml (revision 0)
+++ lams_bb_integration/conf/lams-course-copy/web.xml (revision 0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43)
@@ -0,0 +1,4 @@
+
+
+ LAMS Course Cloning Handler
+
Index: lams_bb_integration/src/org/lamsfoundation/ld/cxcomponent/CxComponentImpl.java
===================================================================
diff -u
--- lams_bb_integration/src/org/lamsfoundation/ld/cxcomponent/CxComponentImpl.java (revision 0)
+++ lams_bb_integration/src/org/lamsfoundation/ld/cxcomponent/CxComponentImpl.java (revision 0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43)
@@ -0,0 +1,187 @@
+package org.lamsfoundation.ld.cxcomponent;
+
+import org.apache.log4j.Logger;
+import org.lamsfoundation.ld.integration.blackboard.GradebookServlet;
+import org.lamsfoundation.ld.integration.blackboard.LamsSecurityUtil;
+
+import blackboard.base.BbList;
+import blackboard.data.ValidationException;
+import blackboard.data.content.Content;
+import blackboard.data.course.CourseMembership;
+import blackboard.data.navigation.CourseToc;
+import blackboard.data.user.User;
+import blackboard.persist.BbPersistenceManager;
+import blackboard.persist.Id;
+import blackboard.persist.PersistenceException;
+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.platform.BbServiceManager;
+import blackboard.platform.cx.component.CopyControl;
+import blackboard.platform.cx.component.CxComponent;
+import blackboard.platform.cx.component.ExportControl;
+import blackboard.platform.cx.component.GenericPackageEntry;
+import blackboard.platform.cx.component.ImportControl;
+import blackboard.portal.data.ExtraInfo;
+import blackboard.portal.data.PortalExtraInfo;
+import blackboard.portal.servlet.PortalUtil;
+
+public class CxComponentImpl implements CxComponent {
+
+ private static Logger logger = Logger.getLogger(CxComponentImpl.class);
+
+ @Override
+ public void doCopy(CopyControl copyControl) {
+
+ Id destinationCourseId = copyControl.getDestinationCourseId();
+ Id sourceCourseId = copyControl.getSourceCourseId();
+
+ try {
+
+ BbPersistenceManager bbPm = BbServiceManager.getPersistenceService().getDbPersistenceManager();
+ ContentDbLoader contentLoader = ContentDbLoader.Default.getInstance();
+ CourseDbLoader cLoader = CourseDbLoader.Default.getInstance();
+ CourseMembershipDbLoader courseMemLoader = CourseMembershipDbLoader.Default.getInstance();
+
+ // find the main teacher
+ BbList monitorCourseMemberships = courseMemLoader.loadByCourseIdAndRole(sourceCourseId,
+ CourseMembership.Role.INSTRUCTOR, null, true);
+ if (monitorCourseMemberships.isEmpty()) {
+ BbList teachingAssistantCourseMemberships = courseMemLoader
+ .loadByCourseIdAndRole(sourceCourseId, CourseMembership.Role.TEACHING_ASSISTANT, null, true);
+ monitorCourseMemberships.addAll(teachingAssistantCourseMemberships);
+ if (monitorCourseMemberships.isEmpty()) {
+ BbList courseBuilderCourseMemberships = courseMemLoader
+ .loadByCourseIdAndRole(sourceCourseId, CourseMembership.Role.COURSE_BUILDER, null, true);
+ monitorCourseMemberships.addAll(courseBuilderCourseMemberships);
+ }
+ }
+ // validate method parameter
+ if (monitorCourseMemberships.isEmpty()) {
+ throw new RuntimeException("There are no monitors in the course courseId=" + sourceCourseId);
+ }
+ User teacher = monitorCourseMemberships.get(0).getUser();
+
+ // get a CourseTOC (Table of Contents) loader. We will need this to iterate through all of the "areas"
+ // within the course
+ CourseTocDbLoader cTocDbLoader = (CourseTocDbLoader) bbPm.getLoader(CourseTocDbLoader.TYPE);
+ BbList courseTocs = cTocDbLoader.loadByCourseId(destinationCourseId);
+
+ System.out.println("Starting clonning course (courseId="+sourceCourseId + ").");
+
+ // 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())) {
+ String lsId = content.getLinkRef();
+
+ String courseIdReadable = cLoader.loadById(destinationCourseId).getCourseId();
+
+ // update lesson id
+ final Long newLessonId = LamsSecurityUtil.cloneLesson(teacher, courseIdReadable, lsId);
+ content.setLinkRef(Long.toString(newLessonId));
+
+ // update URL
+ String contentUrl = content.getUrl();
+ contentUrl = replaceParameterValue(contentUrl, "lsid", Long.toString(newLessonId));
+
+ PkId coursePkId = (PkId) destinationCourseId;
+ String course_id = "_" + coursePkId.getPk1() + "_" + coursePkId.getPk2();
+ contentUrl = replaceParameterValue(contentUrl, "course_id", course_id);
+
+ PkId contentPkId = (PkId) content.getId();
+ String content_id = "_" + contentPkId.getPk1() + "_" + contentPkId.getPk2();
+ contentUrl = replaceParameterValue(contentUrl, "content_id", content_id);
+ content.setUrl(contentUrl);
+
+ // persist updated content
+ ContentDbPersister persister = (ContentDbPersister) bbPm
+ .getPersister(ContentDbPersister.TYPE);
+ 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
+
+ System.out.println("Lesson (lessonId="+lsId + ") was successfully cloned to the one (lessonId="+newLessonId+").");
+ }
+
+ }
+ }
+ }
+
+ } catch (PersistenceException e) {
+ throw new RuntimeException(e);
+ } catch (ValidationException e) {
+ throw new RuntimeException(e);
+ }
+
+ System.out.println("Course (courseId="+sourceCourseId + ") was successfully cloned to the new one (courseId="+destinationCourseId+").");
+
+ }
+
+ @Override
+ public void doExport(ExportControl exportControl) {
+ }
+
+ @Override
+ public void doImport(GenericPackageEntry entry, ImportControl importControl) {
+ }
+
+ public String getName() {
+ return "Sample Content";
+ }
+
+ public String getComponentHandle() {
+ return "lams-sample-content";
+ }
+
+ public Usage getUsage() {
+ return Usage.CONFIGURABLE;
+ }
+
+ public String getApplicationHandle() {
+ return "lams-copy-archive";
+ }
+
+ @Override
+ public void afterImportContent(Content arg0, ImportControl arg1) {
+ }
+
+ 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;
+ }
+
+}
Index: lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/LamsSecurityUtil.java
===================================================================
diff -u -r744501dd7374ab97efc759cb3aae74eb23ea3979 -r0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43
--- lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/LamsSecurityUtil.java (.../LamsSecurityUtil.java) (revision 744501dd7374ab97efc759cb3aae74eb23ea3979)
+++ lams_bb_integration/src/org/lamsfoundation/ld/integration/blackboard/LamsSecurityUtil.java (.../LamsSecurityUtil.java) (revision 0bb440cf6cd13fdca5e7792b1cfa1aed5bbcde43)
@@ -562,6 +562,84 @@
}
/**
+ * Clones lessons in lams through a LAMS webservice using the lsID & courseId parameter.
+ *
+ * @param courseId
+ * courseId as a request parameter
+ * @param ldId
+ * the learning design id for which you wish to start a lesson
+ *
+ * @return lesson id of a cloned lesson
+ */
+ public static Long cloneLesson(User teacher, String courseId, String lsId) {
+
+ String serverId = getServerID();
+ String serverAddr = getServerAddress();
+ String serverKey = getServerKey();
+ String username = teacher.getUserName();
+ String locale = teacher.getLocale();
+ String country = getCountry(locale);
+ String lang = getLanguage(locale);
+
+ if (courseId == null || serverId == null || serverAddr == null || serverKey == null) {
+ logger.info("Unable to clone lesson, one or more lams configuration properties or the course id is null");
+ throw new RuntimeException("Unable to clone lesson, one or more lams configuration properties or the course id is null. courseId="+courseId);
+ }
+
+ try {
+ String method = "clone";
+ String timestamp = new Long(System.currentTimeMillis()).toString();
+// String username = "not_available";//signifier for skipping security check as we can't get the current user requested cloning
+ String hash = generateAuthenticationHash(timestamp, username, serverId);
+ String serviceURL = serverAddr + "/services/xml/LessonManager?" + "serverId="
+ + URLEncoder.encode(serverId, "utf8") + "&datetime=" + timestamp + "&username="
+ + URLEncoder.encode(username, "utf8") + "&hashValue=" + hash + "&courseId="
+ + URLEncoder.encode(courseId, "UTF8") + "&country="
+ + 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();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ Document document = db.parse(is);
+
+ // get the lesson id from the response
+
+ /*
+ * The getTextContext is not a java 1.4 method, so Blackboard 7.1 comes up with errors using getNodeValue()
+ * instead
+ */
+ // return Long.parseLong(document.getElementsByTagName("Lesson").item(0).getAttributes().getNamedItem("lessonId").getTextContent());
+ return Long.parseLong(document.getElementsByTagName("Lesson").item(0).getAttributes()
+ .getNamedItem("lessonId").getNodeValue());
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Unable to clone LAMS lesson, bad URL: '" + serverAddr
+ + "', please check lams.properties", e);
+ } catch (IllegalStateException e) {
+ throw new RuntimeException(
+ "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator",
+ e);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Unable to clone LAMS lesson, RMI Remote Exception", e);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Unable to clone LAMS lesson, Unsupported Encoding Exception", e);
+ } catch (ConnectException e) {
+ throw new RuntimeException(
+ "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);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to clone LAMS lesson. Please contact your system administrator.", e);
+ }
+
+ }
+
+ /**
* Pre-adding students and monitors to a lesson
*
* @param ctx