Skip to content

Commit

Permalink
SAK-50980 Kernel migrate embedded image links in RTE during cc+import (
Browse files Browse the repository at this point in the history
  • Loading branch information
csev authored Feb 24, 2025
1 parent c3d8a33 commit cedb14e
Show file tree
Hide file tree
Showing 15 changed files with 261 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<property name="aliasService"><ref bean="org.sakaiproject.alias.api.AliasService"/></property>
<property name="preferencesService"><ref bean="org.sakaiproject.user.api.PreferencesService"/></property>
<property name="ltiService"><ref bean="org.sakaiproject.lti.api.LTIService"/></property>
<property name="linkMigrationHelper" ref="org.sakaiproject.util.api.LinkMigrationHelper"/>
<property name="toolManager"><ref bean="org.sakaiproject.tool.api.ToolManager"/></property>
<property name="siteEmailNotificationAnnc"><ref bean="org.sakaiproject.announcement.impl.SiteEmailNotificationAnnc"/></property>
<property name="containerTableName"><value>ANNOUNCEMENT_CHANNEL</value></property>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,18 @@ private void addSupplementaryItemAttachments(Document doc, Element item, List<St
public String merge(String siteId, Element root, String archivePath, String fromSiteId, String creatorId, Map<String, String> attachmentNames,
Map<Long, Map<String, Object>> ltiContentItems, Map<String, String> userIdTrans, Set<String> userListAllowImport) {


String archiveContext = "";
String archiveServerUrl = "";

Node parent = root.getParentNode();
if (parent.getNodeType() == Node.ELEMENT_NODE)
{
Element parentEl = (Element)parent;
archiveContext = parentEl.getAttribute("source");
archiveServerUrl = parentEl.getAttribute("serverurl");
}

final StringBuilder results = new StringBuilder();
results.append("begin merging ").append(getLabel()).append(" context ").append(siteId).append(LINE_SEPARATOR);
final NodeList allChildrenNodeList = root.getChildNodes();
Expand All @@ -454,7 +466,7 @@ public String merge(String siteId, Element root, String archivePath, String from
for (Element assignmentElement : assignmentElements) {

try {
mergeAssignment(siteId, assignmentElement, results, creatorId, assignmentTitles, attachmentNames, ltiContentItems);
mergeAssignment(siteId, assignmentElement, results, creatorId, assignmentTitles, attachmentNames, ltiContentItems, archiveContext, archiveServerUrl);
assignmentsMerged++;
} catch (Exception e) {
final String error = "could not merge assignment with id: " + assignmentElement.getFirstChild().getFirstChild().getNodeValue();
Expand Down Expand Up @@ -990,7 +1002,7 @@ public String getTimeSpent(AssignmentSubmission submission) {
}

@Transactional
private Assignment mergeAssignment(final String siteId, final Element element, final StringBuilder results, String creatorId, Set<String> assignmentTitles, Map<String, String> attachmentNames, Map<Long, Map<String, Object>> ltiContentItems) throws PermissionException {
private Assignment mergeAssignment(final String siteId, final Element element, final StringBuilder results, String creatorId, Set<String> assignmentTitles, Map<String, String> attachmentNames, Map<Long, Map<String, Object>> ltiContentItems, String archiveContext, String archiveServerUrl) throws PermissionException {

if (!allowAddAssignment(siteId)) {
throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_ADD_ASSIGNMENT, AssignmentReferenceReckoner.reckoner().context(siteId).reckon().getReference());
Expand All @@ -1016,6 +1028,7 @@ private Assignment mergeAssignment(final String siteId, final Element element, f
if ( StringUtils.isNotEmpty(creatorId) ) assignmentFromXml.setAuthor(creatorId);

String newInstructions = ltiService.fixLtiLaunchUrls(assignmentFromXml.getInstructions(), siteId, ltiContentItems);
newInstructions = linkMigrationHelper.migrateLinksInMergedRTE(siteId, archiveContext, archiveServerUrl, newInstructions);
assignmentFromXml.setInstructions(newInstructions);

Long contentKey = ltiService.mergeContentFromImport(element, siteId);
Expand Down Expand Up @@ -1092,12 +1105,9 @@ private Assignment mergeAssignment(final String siteId, final Element element, f
for (int k=0; k<attachments.getLength(); ++k) {
Element attachmentElement = (Element) attachments.item(k);
String attachmentId = attachmentElement.getTextContent();
System.out.println("attachmentId="+attachmentId);
AssignmentSupplementItemAttachment attachment = assignmentSupplementItemService.newAttachment();
System.out.println("attachmentNames="+attachmentNames);
String fromContext = null; // No-Op, there is no fromContext when importing from a ZIP
String nAttachId = transferAttachment(fromContext, siteId, attachmentId, attachmentNames);
System.out.println("saved attachment="+nAttachId);
attachment.setAssignmentSupplementItemWithAttachment(copy);
attachment.setAttachmentId(nAttachId);
assignmentSupplementItemService.saveAttachment(attachment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.util.*;
import org.sakaiproject.util.cover.LinkMigrationHelper;
import org.sakaiproject.util.api.LinkMigrationHelper;

import org.sakaiproject.assignment.api.AssignmentServiceConstants;
import org.sakaiproject.api.app.messageforums.DiscussionForumService;
Expand Down Expand Up @@ -152,7 +152,7 @@ public abstract class BaseCalendarService implements CalendarService, DoubleStor
@Setter protected FunctionManager functionManager;
@Setter protected EventTrackingService eventTrackingService;
@Setter protected OpaqueUrlDao opaqueUrlDao;

@Setter protected LinkMigrationHelper linkMigrationHelper;
private PDFExportService pdfExportService;

private GroupComparator groupComparator = new GroupComparator();
Expand Down Expand Up @@ -1492,6 +1492,18 @@ public String archive(String siteId, Document doc, Stack stack, String archivePa
public String merge(String siteId, Element root, String archivePath, String fromSiteId, String creatorId, Map<String, String> attachmentImportMap,
Map<Long, Map<String, Object>> ltiContentItems, Map<String, String> userIdTrans, Set<String> userListAllowImport)
{

String archiveContext = "";
String archiveServerUrl = "";

Node parent = root.getParentNode();
if (parent.getNodeType() == Node.ELEMENT_NODE)
{
Element parentEl = (Element)parent;
archiveContext = parentEl.getAttribute("source");
archiveServerUrl = parentEl.getAttribute("serverurl");
}

// prepare the buffer for the results log
StringBuilder results = new StringBuilder();

Expand Down Expand Up @@ -1634,6 +1646,7 @@ public String merge(String siteId, Element root, String archivePath, String from
}
String description = edit.getDescriptionFormatted();
description = ltiService.fixLtiLaunchUrls(description, siteId, ltiContentItems);
description = linkMigrationHelper.migrateLinksInMergedRTE(siteId, archiveContext, archiveServerUrl, description);
edit.setDescriptionFormatted(description);

calendar.commitEvent(edit);
Expand Down Expand Up @@ -1897,7 +1910,7 @@ public void updateEntityReferences(String toContext, Map<String, String> transve
String msgBodyFormatted = ce.getDescriptionFormatted();
boolean updated = false;
StringBuffer msgBodyPreMigrate = new StringBuffer(msgBodyFormatted);
msgBodyFormatted = LinkMigrationHelper.migrateAllLinks(entrySet, msgBodyFormatted);
msgBodyFormatted = linkMigrationHelper.migrateAllLinks(entrySet, msgBodyFormatted);
if(!msgBodyFormatted.equals(msgBodyPreMigrate.toString())){

CalendarEventEdit edit = calendarObj.getEditEvent(ce.getId(), org.sakaiproject.calendar.api.CalendarService.EVENT_MODIFY_CALENDAR);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<property name="userDirectoryService" ref="org.sakaiproject.user.api.UserDirectoryService" />
<property name="usageSessionService" ref="org.sakaiproject.event.api.UsageSessionService" />
<property name="ltiService" ref="org.sakaiproject.lti.api.LTIService" />
<property name="linkMigrationHelper" ref="org.sakaiproject.util.api.LinkMigrationHelper" />
<property name="externalCalendarSubscriptionService" ref="org.sakaiproject.calendar.api.ExternalCalendarSubscriptionService" />
<property name="opaqueUrlDao" ref="org.sakaiproject.calendar.api.OpaqueUrlDao" />
<property name="containerTableName"><value>CALENDAR_CALENDAR</value></property>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,15 @@ public interface LinkMigrationHelper {
*/
public String migrateOneLink(String fromContextRef, String targetContextRef, String msgBody);

/*
* Do several transformations to migrate the content links present in a zip import of RTE content
*
* @param siteId the site id (Must not be null or empty)
* @param fromContext the context of the original content (Can be null)
* @param fromServerUrl the server url of the original content (Can be null)
* @param content the content to migrate
* @return the migrated content
*/
public String migrateLinksInMergedRTE(String siteId, String fromContext, String fromServerUrl, String content);

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ public static String migrateOneLink(String fromContextRef, String targetContextR

return getLinkMigrationHelper().migrateOneLink(fromContextRef, targetContextRef, msgBody);
}

public static String migrateLinksInMergedRTE(String siteId, String fromContext, String fromServerUrl, String content) {
return getLinkMigrationHelper().migrateLinksInMergedRTE(siteId, fromContext, fromServerUrl, content);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public class LinkMigrationHelperImpl implements LinkMigrationHelper {
private static final String[] ENCODED_IMAGE = {"data:image"};
private static final String[] SHORTENER_STRINGS = {"/x/", "bit.ly"};

public static final String ACCESS_CONTENT_GROUP = "/access/content/group/";
public static final String DIRECT_LINK = "/direct/";

private ServerConfigurationService serverConfigurationService;

public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) {
Expand Down Expand Up @@ -130,6 +133,62 @@ public String migrateOneLink(String fromContextRef, String targetContextRef, Str
return expandedMsgBody;
}

/*
* Do several transformations to migrate the content links present in a zip import of RTE content
*
* @param siteId the site id (Must not be null or empty)
* @param fromContext the context of the original content (Can be null)
* @param fromServerUrl the server url of the original content (Can be null)
* @param content the content to migrate
* @return the migrated content
*/
public String migrateLinksInMergedRTE(String siteId, String fromContext, String fromServerUrl, String content) {
String before;
String after = serverConfigurationService.getServerUrl() + ACCESS_CONTENT_GROUP + siteId + "/";

// Replace full match of the fromserverUrl and fromContext (ideal)
if ( StringUtils.isNotBlank(fromServerUrl) && StringUtils.isNotBlank(fromContext) ) {
before = fromServerUrl + ACCESS_CONTENT_GROUP + fromContext + "/";
content = content.replace(before, after);
}

// If we don't know the fromServerUrl, but we know the fromContext, replace athe url prefix to get the links onto this server
if ( StringUtils.isBlank(fromServerUrl) && StringUtils.isNotBlank(fromContext) ) {
before = "https?://[^/]+/" + ACCESS_CONTENT_GROUP + fromContext + "/";
content = content.replaceAll(before, after);
}

// If we know the fromContext, we can replace urls that start with "/"
if ( StringUtils.isNotBlank(fromContext) ) {
before = "\"/" + ACCESS_CONTENT_GROUP + fromContext + "/";
after = "\"/" + ACCESS_CONTENT_GROUP + siteId + "/";
content = content.replaceAll(before, after);
}

// If we know the fromServerUrl and not the fromContext, move old broken urls to this server at a minimum
if ( StringUtils.isNotBlank(fromServerUrl) ) {
before = fromServerUrl + ACCESS_CONTENT_GROUP;
after = serverConfigurationService.getServerUrl() + ACCESS_CONTENT_GROUP;
content = content.replace(before, after);
}

// [http://localhost:8080/direct/forum_topic/1]
// Migrate direct links to the new server if we have the name of the server
if ( StringUtils.isNotBlank(fromServerUrl) ) {
before = fromServerUrl + DIRECT_LINK;
after = serverConfigurationService.getServerUrl() + DIRECT_LINK;
content = content.replace(before, after);
}

try {
content = this.bracketAndNullifySelectedLinks(content);
} catch (Exception e) {
log.debug("Error bracketing and nullifying links: " + e.toString());
}

return content;
}

private List<String> findLinks(String msgBody) throws Exception {

List<String> links = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public void setUp() {
impl.setServerConfigurationService(serverConfigurationService);
// Always return defaults
when(serverConfigurationService.getString(anyString(), anyString())).then(a -> a.getArgument(1));
when(serverConfigurationService.getServerUrl()).thenReturn("http://localhost:8080");
}

@Test
Expand All @@ -66,4 +67,61 @@ public void testMigrateOneLink() {
assertEquals("this <a href='/url/newId'>newId</a>", impl.migrateOneLink("oldId", "newId", "this <a href='/url/oldId'>oldId</a>"));
}

@Test
public void testMigrateLinksInMergedRTE() {
// Test basic migration of oldId to newId
String content = "this <a href='http://www.zap.com/access/content/group/oldId/ietf-postel-06.png'>text</a>";
String migrated = impl.migrateLinksInMergedRTE("newId", "oldId", "http://www.zap.com", content);
String result = "this <a href='http://localhost:8080/access/content/group/newId/ietf-postel-06.png'>text</a>";
assertEquals(result, migrated);

// Test link with different site ID (should remain unchanged)
content = "this <a href='http://www.zap.com/access/content/group/weirdId/ietf-postel-06.png'>text</a>";
migrated = impl.migrateLinksInMergedRTE("newId", "oldId", "http://www.zap.com", content);
result = "this <a href='http://localhost:8080/access/content/group/weirdId/ietf-postel-06.png'>text</a>";
assertEquals(result, migrated);

// Test multiple links in the same content
content = "this <a href='http://www.zap.com/access/content/group/oldId/file1.pdf'>link1</a> and " +
"<a href='http://www.zap.com/access/content/group/oldId/file2.jpg'>link2</a>";
migrated = impl.migrateLinksInMergedRTE("newId", "oldId", "http://www.zap.com", content);
result = "this <a href='http://localhost:8080/access/content/group/newId/file1.pdf'>link1</a> and " +
"<a href='http://localhost:8080/access/content/group/newId/file2.jpg'>link2</a>";
assertEquals(result, migrated);

// Test with different source domain
content = "this <a href='https://other-domain.com/access/content/group/oldId/file.pdf'>text</a>";
migrated = impl.migrateLinksInMergedRTE("newId", "oldId", "https://other-domain.com", content);
result = "this <a href='http://localhost:8080/access/content/group/newId/file.pdf'>text</a>";
assertEquals(result, migrated);

// Test with non-matching URL pattern (should remain unchanged)
content = "this <a href='http://www.zap.com/different/path/oldId/file.pdf'>text</a>";
migrated = impl.migrateLinksInMergedRTE("newId", "oldId", "http://www.zap.com", content);
result = "this <a href='http://www.zap.com/different/path/oldId/file.pdf'>text</a>";
assertEquals(result, migrated);

// Test direct link migration
content = "Check this discussion [http://www.zap.com/direct/forum_topic/123]";
migrated = impl.migrateLinksInMergedRTE("newId", "oldId", "http://www.zap.com", content);
result = "Check this discussion [http://localhost:8080/direct/forum_topic/123]";
assertEquals(result, migrated);

// Test multiple direct links in the same content
content = "First topic [http://www.zap.com/direct/forum_topic/123] and " +
"second topic [http://www.zap.com/direct/forum_topic/456]";
migrated = impl.migrateLinksInMergedRTE("newId", "oldId", "http://www.zap.com", content);
result = "First topic [http://localhost:8080/direct/forum_topic/123] and " +
"second topic [http://localhost:8080/direct/forum_topic/456]";
assertEquals(result, migrated);

// Test mix of direct and content links
content = "Resource <a href='http://www.zap.com/access/content/group/oldId/file.pdf'>here</a> " +
"and discussion [http://www.zap.com/direct/forum_topic/789]";
migrated = impl.migrateLinksInMergedRTE("newId", "oldId", "http://www.zap.com", content);
result = "Resource <a href='http://localhost:8080/access/content/group/newId/file.pdf'>here</a> " +
"and discussion [http://localhost:8080/direct/forum_topic/789]";
assertEquals(result, migrated);
}

}
Loading

0 comments on commit cedb14e

Please sign in to comment.