Introduction
In this post I am going to walk you thru,
- How to migrate images in the rich text area from one instance (org) to another instance (org).
As you already aware, we cannot migrate images inside rich text area field as part of data migration. We have to do some workarounds for migrating all the images to destination organization and make it available as usual in the rich text are fields. This post will give you enough information to achieve the same.
STEP 1: Store rich text area images as documents in the source instance.
Write a controller class which retrieves all the images in the rich text area, convert it to document and store it. Remember that you cannot use batch class for doing this as we cannot use getContent() in batch class.
NOTES:
- You have to combine “RichTextAreaFieldData.csv” & “ContentReference.csv” files from your Data Export and store the result set data on source instance in a custom object or custom setting (let us assume object name as RTA_Image__c). This is the critical input for your image conversion class.
- Make sure you are storing the Content Reference Id in the Description field of the document.
Sample Code
string strUrl = 'https://c.ap1.content.force.com/servlet/rtaImage?';
string strFolderId = 'FoLdERidToStore';
string strQuery = 'select Id, Content_Reference_Id__c, Record_Id__c, Field_Id__c, Image_Name__c, Type__c, Owner_Id__c from RTA_Image__c' + (Test.isRunningTest() ? ' limit 5' : '');
list<Document> lstDocuments = new list<Document>();
for(RTA_Image__c rtaImage : Database.query(strQuery)) {
PageReference pageRef = new PageReference(strUrl + 'eid=' + rtaImage.Record_Id__c + '&feoid=' + rtaImage.Field_Id__c + '&refid=' + rtaImage.Content_Reference_Id__c);
Document doc = new Document();
doc.Name = rtaImage.Image_Name__c;
doc.Type = rtaImage.Type__c;
if(Test.isRunningTest()) {
doc.Body = Blob.valueOf('This is sample text.');
}
else {
doc.Body = pageRef.getContent();
}
doc.FolderId = strFolderId;
doc.Description = Id.valueOf(rtaImage.Content_Reference_Id__c);
lstDocuments.add(doc);
}
if(!lstDocuments.isEmpty()) {
insert lstDocuments;
}
- Make sure you are storing the Content Reference Id in the Description field of the document.
Sample Code
string strUrl = 'https://c.ap1.content.force.com/servlet/rtaImage?';
string strFolderId = 'FoLdERidToStore';
string strQuery = 'select Id, Content_Reference_Id__c, Record_Id__c, Field_Id__c, Image_Name__c, Type__c, Owner_Id__c from RTA_Image__c' + (Test.isRunningTest() ? ' limit 5' : '');
list<Document> lstDocuments = new list<Document>();
for(RTA_Image__c rtaImage : Database.query(strQuery)) {
PageReference pageRef = new PageReference(strUrl + 'eid=' + rtaImage.Record_Id__c + '&feoid=' + rtaImage.Field_Id__c + '&refid=' + rtaImage.Content_Reference_Id__c);
Document doc = new Document();
doc.Name = rtaImage.Image_Name__c;
doc.Type = rtaImage.Type__c;
if(Test.isRunningTest()) {
doc.Body = Blob.valueOf('This is sample text.');
}
else {
doc.Body = pageRef.getContent();
}
doc.FolderId = strFolderId;
doc.Description = Id.valueOf(rtaImage.Content_Reference_Id__c);
lstDocuments.add(doc);
}
if(!lstDocuments.isEmpty()) {
insert lstDocuments;
}
STEP 2: Export all the documents which are stored in that specific folder using Force.com Migration Tool.
STEP 3: Import all the exported documents to target instance using Force.com Migration Tool.
STEP 4: Export all the documents (which you have imported in the previous step) using data loader. You can export Id and Description fields alone. Description and Id fields are critical inputs for next step.
STEP 5: Store the Description and Id values which are exported from the target instance into custom setting or custom object in source instance (you can use the same object or custom setting which is created in Step 1).
STEP 6: Write a batch class in source instance which iterate all the rich text area fields, identify the images, replace the image URL and store the result set in a separate object (let us assume object name as RTA_Value__c). This process should be repeated for all the objects which have rich text area fields on it.
NOTES:
- You cannot write this batch apex in target instance because salesforce is automatically replacing the invalid image URL’s inside the rich text area field with empty image URL while importing.
Sample Code
map<string, Configurations__c> mapDocs = Configurations__c.getAll();
string srcUrl = mapDocs.get('RTA Image URL').Value__c;
string destUrl = mapDocs.get('RTA Image Dest URL').Value__c;
list<RTA_Value__c> lstRTAValues = new list<RTA_Value__c>();
string strRTAFields = strFields.replace('Id, ', '');
for(sObject obj : scope) {
for(string fieldName : strRTAFields.split(', ')) {
string srcRTAValue = String.valueOf(obj.get(fieldName));
if(!String.isEmpty(srcRTAValue)) {
while(srcRTAValue.contains(srcUrl)) {
string oldUrl = srcRTAValue.substring(srcRTAValue.indexOf(srcUrl), srcRTAValue.indexOf('"', srcRTAValue.indexOf(srcUrl)));
string contentRefId = oldUrl.substring(oldUrl.indexOf('refid=') + 6).replace('">', '');
string newUrl = destUrl + 'file=' + mapDocs.get(contentRefId).Value__c;
srcRTAValue = srcRTAValue.replace(oldUrl, newUrl);
}
if(srcRTAValue != String.valueOf(obj.get(fieldName))) {
RTA_Value__c rtaValue = new RTA_Value__c();
rtaValue.PMO_Id__c = Id.valueOf(String.valueOf(obj.get('Id')));
rtaValue.Object_Name__c = strObject;
rtaValue.Field_Name__c = fieldName;
rtaValue.Old_Value__c = String.valueOf(obj.get(fieldName));
rtaValue.New_Value__c = srcRTAValue;
lstRTAValues.add(rtaValue);
}
}
}
}
if(!lstRTAValues.isEmpty()) {
insert lstRTAValues;
}
Sample Code
map<string, Configurations__c> mapDocs = Configurations__c.getAll();
string srcUrl = mapDocs.get('RTA Image URL').Value__c;
string destUrl = mapDocs.get('RTA Image Dest URL').Value__c;
list<RTA_Value__c> lstRTAValues = new list<RTA_Value__c>();
string strRTAFields = strFields.replace('Id, ', '');
for(sObject obj : scope) {
for(string fieldName : strRTAFields.split(', ')) {
string srcRTAValue = String.valueOf(obj.get(fieldName));
if(!String.isEmpty(srcRTAValue)) {
while(srcRTAValue.contains(srcUrl)) {
string oldUrl = srcRTAValue.substring(srcRTAValue.indexOf(srcUrl), srcRTAValue.indexOf('"', srcRTAValue.indexOf(srcUrl)));
string contentRefId = oldUrl.substring(oldUrl.indexOf('refid=') + 6).replace('">', '');
string newUrl = destUrl + 'file=' + mapDocs.get(contentRefId).Value__c;
srcRTAValue = srcRTAValue.replace(oldUrl, newUrl);
}
if(srcRTAValue != String.valueOf(obj.get(fieldName))) {
RTA_Value__c rtaValue = new RTA_Value__c();
rtaValue.PMO_Id__c = Id.valueOf(String.valueOf(obj.get('Id')));
rtaValue.Object_Name__c = strObject;
rtaValue.Field_Name__c = fieldName;
rtaValue.Old_Value__c = String.valueOf(obj.get(fieldName));
rtaValue.New_Value__c = srcRTAValue;
lstRTAValues.add(rtaValue);
}
}
}
}
if(!lstRTAValues.isEmpty()) {
insert lstRTAValues;
}
STEP 7: Export the values stored in RTA_Value__c object and prepare a csv file for importing. You can use Object_Name__c and Field_Name__c fields for preparing the csv file.
STEP 8: Import the new rich text area field values to the corresponding objects in the target instance.
Conclusion
You should be able to migrate and retain the images inside rich text area field by following above steps in a proper way. Some important facts of this workaround are,
- Rich text area images are stored in documents in the target instance and it is served from there for rich text area. Usually salesforce stores rich text area field images in a separate server and serves from there.
- This workaround considers images in the rich text area only and not any other components like links which points to source instance document / location.