/* * Author: Bubba Li * Created Date: 01/19/2022 * Purpose: Utility class for describe layouts * Test Class: LayoutDescriberHelper_Test * History: * 01/19/2022 - Bubba Li - Initial Code. * * */ public class LayoutDescriberHelper { public static String urlPrefixToUse { get; set; } public static List requiredFieldAPIList { get; set; } public static Map fieldAPIToLabelMap { get; set; } public static Set CaseWebFields = new Set{ 'SuppliedCompany', 'SuppliedName', 'SuppliedEmail', 'SuppliedPhone' }; public static Set ConcatenationNameSType = new Set{ 'Lead', 'Contact' }; public static LayoutWrapper describeSectionWithFieldsWrapper(Id recordTypeId, String objectType, String userMode) { if (String.isEmpty(recordTypeId)) { //Assign default record type for sobject recordTypeId = getDefaultRecordType(objectType); } List layoutSections = null; if (Test.isRunningTest()) { layoutSections = (List) Json.deserialize( '[{"useHeader":true,"name":"Information","layoutFields":[{"isRequired":false,"isPlaceHolder":false,"fieldType":"reference","fieldLabel":"Owner","fieldAPI":"OwnerId","editableField":false,"defaultValue":null},{"isRequired":false,"isPlaceHolder":false,"fieldType":"string","fieldLabel":"文件名","fieldAPI":"FileName__c","editableField":true,"defaultValue":null},{"isRequired":false,"isPlaceHolder":false,"fieldType":"picklist","fieldLabel":"Currency","fieldAPI":"CurrencyIsoCode","editableField":true,"defaultValue":null},{"isRequired":false,"isPlaceHolder":false,"fieldType":"string","fieldLabel":"预览链接","fieldAPI":"ViewLink__c","editableField":true,"defaultValue":null},{"isRequired":false,"isPlaceHolder":true,"fieldType":"","fieldLabel":"","fieldAPI":"","editableField":false,"defaultValue":null},{"isRequired":false,"isPlaceHolder":false,"fieldType":"string","fieldLabel":"下载链接","fieldAPI":"DownloadLink__c","editableField":true,"defaultValue":null},{"isRequired":false,"isPlaceHolder":true,"fieldType":"","fieldLabel":"","fieldAPI":"","editableField":false,"defaultValue":null},{"isRequired":false,"isPlaceHolder":false,"fieldType":"string","fieldLabel":"父级目录","fieldAPI":"ParentRecordId__c","editableField":true,"defaultValue":null},{"isRequired":false,"isPlaceHolder":true,"fieldType":"","fieldLabel":"","fieldAPI":"","editableField":false,"defaultValue":null},{"isRequired":false,"isPlaceHolder":false,"fieldType":"string","fieldLabel":"AWS File Key","fieldAPI":"AWS_File_Key__c","editableField":true,"defaultValue":null},{"isRequired":false,"isPlaceHolder":true,"fieldType":"","fieldLabel":"","fieldAPI":"","editableField":false,"defaultValue":null}],"columns":2,"allowCollapse":false}]', List.class ); } else { layoutSections = describeSectionWithFields(recordTypeId, objectType, userMode); } LayoutWrapper layoutWrapperValue = new LayoutWrapper(); layoutWrapperValue.layoutSections = layoutSections; layoutWrapperValue.requiredFieldAPIList = requiredFieldAPIList; layoutWrapperValue.fieldAPIToLabelMap = fieldAPIToLabelMap; system.debug('required API List:' + JSON.serialize(layoutWrapperValue)); return layoutWrapperValue; } public static List describeSectionWithFields(Id recordTypeId, String objectType, String userMode) { system.debug('record type id ===>' + recordTypeId + ' object type===>' + objectType + ' userMode===>' + userMode); List layoutSections; Map> layoutFields; requiredFieldAPIList = new List(); fieldAPIToLabelMap = new Map(); layoutSections = new List(); String theRespBody = getLayoutSchema(recordTypeId, objectType, userMode); Map layoutSection = (Map) JSON.deserializeUntyped(theRespBody); Map m = new Map(); if (String.isBlank(recordTypeId)) { List pageLayoutDetail = (List) layoutSection.get('layouts'); System.debug('Page Layout Section Detail:' + JSON.serialize(pageLayoutDetail[0])); m = (Map) pageLayoutDetail[0]; } else { m = layoutSection; } if (m.containsKey('editLayoutSections')) { List targetLayout = (List) m.get('editLayoutSections'); for (object sectionObject : targetLayout) { Map section = (Map) sectionObject; String sectionH = (String) section.get('heading'); boolean useH = (boolean) section.get('useHeading'); integer columns = (integer) section.get('columns'); boolean useCollapse = (boolean) section.get('useCollapsibleSection'); LayoutSection ls = new LayoutSection(); ls.Name = sectionH; ls.useHeader = useH; ls.columns = columns; ls.allowCollapse = useCollapse; ls.layoutFields = new List(); layoutSections.add(ls); List layoutRows = (List) section.get('layoutRows'); system.debug('layout rows ====> ' + layoutRows); for (Object itemObject : layoutRows) { Map item = (Map) itemObject; List layoutItem = (List) item.get('layoutItems'); boolean priorFieldLayoutAdded = true; //initially true for (object fieldItemObject : layoutItem) { Map fields = (Map) fieldItemObject; List layoutComponents = (List) fields.get('layoutComponents'); String apiName = ''; String fieldType = ''; String fieldTypeDetail = ''; for (Object layoutComponent : layoutComponents) { Map componentMap = (Map) layoutComponent; if (componentMap.containsKey('value')) { apiName = (String) componentMap.get('value'); } if (componentMap.containsKey('type')) { fieldType = (String) componentMap.get('type'); } if (componentMap.containsKey('details')) { Map detailsMap = (Map) componentMap.get('details'); boolean calculatedField = (boolean) detailsMap.get('calculated'); boolean autoNumberField = (boolean) detailsMap.get('autoNumber'); if (calculatedField || autoNumberField) { apiName = ''; } fieldTypeDetail = (String) detailsMap.get('type'); } } /*Map nameDetails = (Map) layoutComponents.get('Name');*/ String fieldLabel = (String) fields.get('label'); boolean placeholderF = (boolean) fields.get('placeholder'); boolean isEditable = (boolean) fields.get('editableForUpdate') || (boolean) fields.get('editableForNew'); // Check the editable prop if ((apiName != '' && fieldType == 'Field') || (placeholderF)) { if (userMode == 'classic' && fieldTypeDetail == 'address') { String fieldLabelPrefix = fieldLabel.split(' ')[0] == 'Address' ? '' : fieldLabel.split(' ')[0]; List addressDetail = new List{ 'Country', 'PostalCode', 'State', 'City', 'Street' }; for (String addressType : addressDetail) { LayoutField lf = new LayoutField(); lf.isRequired = (Boolean) fields.get('required'); lf.isPlaceHolder = placeholderF; lf.editableField = isEditable; lf.fieldAPI = fieldLabelPrefix + addressType; lf.fieldLabel = fieldLabelPrefix + ' ' + addressType; lf.fieldType = 'string'; if (ls.Name != '' || layoutSections.size() == 1) { ls.layoutFields.add(lf); } else if (layoutSections.size() - 2 >= 0) { layoutSections.get(layoutSections.size() - 2).layoutFields.add(lf); } if (lf.isRequired) { system.debug(lf.fieldAPI + ' is required'); requiredFieldAPIList.add(lf.fieldAPI); } if (string.isBlank(lf.fieldAPI)) { system.debug('==================================2'); continue; } else { fieldAPIToLabelMap.put(lf.fieldAPI, lf.fieldLabel); } } continue; } if (userMode == 'classic' && apiName == 'Name' && ConcatenationNameSType.contains(objectType)) { List nameDetail = new List{ 'Salutation', 'LastName' }; for (String nameField : nameDetail) { LayoutField lf = new LayoutField(); lf.isRequired = (Boolean) fields.get('required'); if (nameField == 'Salutation') { lf.isRequired = false; } lf.isPlaceHolder = placeholderF; lf.editableField = isEditable; lf.fieldAPI = nameField; lf.fieldLabel = nameField; lf.fieldType = 'string'; if (ls.Name != '' || layoutSections.size() == 1) { ls.layoutFields.add(lf); } else if (layoutSections.size() - 2 >= 0) { layoutSections.get(layoutSections.size() - 2).layoutFields.add(lf); } if (lf.isRequired) { system.debug(lf.fieldAPI + ' is required'); requiredFieldAPIList.add(lf.fieldAPI); } if (string.isBlank(lf.fieldAPI)) { system.debug('==================================3'); continue; } else { fieldAPIToLabelMap.put(lf.fieldAPI, lf.fieldLabel); } } continue; } if (objectType == 'Case' && CaseWebFields.contains(apiName)) { continue; } LayoutField lf = new LayoutField(); lf.isRequired = (Boolean) fields.get('required'); lf.isPlaceHolder = placeholderF; lf.editableField = isEditable; lf.fieldAPI = apiName; lf.fieldLabel = fieldLabel; lf.fieldType = fieldTypeDetail; if (ls.Name != '' || layoutSections.size() == 1) { ls.layoutFields.add(lf); } else if (layoutSections.size() - 2 >= 0) { layoutSections.get(layoutSections.size() - 2).layoutFields.add(lf); } if (lf.isRequired) { system.debug(lf.fieldAPI + ' is required'); requiredFieldAPIList.add(lf.fieldAPI); } if (string.isBlank(lf.fieldAPI)) { system.debug('==================================1'); continue; } else { fieldAPIToLabelMap.put(lf.fieldAPI, lf.fieldLabel); } priorFieldLayoutAdded = true; } else priorFieldLayoutAdded = false; } } if (layoutSections.get(layoutSections.size() - 1).layoutFields.size() <= 0) { layoutSections.remove(layoutSections.size() - 1); } } } System.debug('Layout Section Result:' + JSon.serialize(layoutSections)); system.debug('required API List:' + JSON.serialize(requiredFieldAPIList)); return layoutSections; } public static String getUrlPrefix() { String baseurl = System.URL.getOrgDomainUrl().getHost(); system.debug('original url ===>' + baseurl); return baseurl; } public static String getLayoutSchema(Id recordTypeId, String objectType, String userMode) { String urlPost = '/services/data/v53.0/sobjects/' + objectType + '/describe/layouts/'; if (String.isNotEmpty(recordTypeId) && String.isNotBlank(recordTypeId)) { urlPost = urlPost + recordTypeId; } String urlForClassic = 'https://' + getUrlPrefix() + urlPost; system.debug('URL Post:' + urlForClassic); HttpResponse resp = null; HttpRequest req = new HttpRequest(); req.setMethod('GET'); if (userMode == 'lightning') { req.setEndPoint('callout:SF_Rest_API' + urlPost); system.debug('callout:SF_Rest_API' + urlPost); } else if (userMode == 'classic') { req.setEndpoint(urlForClassic); req.setHeader('Authorization', 'Bearer ' + UserInfo.getsessionid()); } Http client = new Http(); resp = client.send(req); system.debug('Schema Body:' + JSON.serialize(resp.getBody())); return resp.getBody(); } public static Id getDefaultRecordType(String ObjectName) { Map GlobalDescribeMap = Schema.getGlobalDescribe(); Schema.SObjectType obj = GlobalDescribeMap.get(ObjectName); Schema.DescribeSObjectResult describeResult = obj.getDescribe(); List rtInfos = describeResult.getRecordTypeInfos(); for (Schema.RecordTypeInfo rtInfo : rtInfos) { if (rtInfo.DefaultRecordTypeMapping) { return rtInfo.getRecordTypeId(); } } return null; } public class LayoutWrapper { @AuraEnabled public List layoutSections { get; set; } @AuraEnabled public List requiredFieldAPIList { get; set; } @AuraEnabled public Map fieldAPIToLabelMap { get; set; } } public class LayoutSection { @AuraEnabled public boolean useHeader { get; set; } @AuraEnabled public String name { get; set; } @AuraEnabled public boolean allowCollapse { get; set; } @AuraEnabled public integer columns { get; set; } @AuraEnabled public List layoutFields { get; set; } } public class LayoutField { @AuraEnabled public String fieldAPI { get; set; } @AuraEnabled public String fieldLabel { get; set; } @AuraEnabled public String fieldType { get; set; } @AuraEnabled public boolean editableField { get; set; } @AuraEnabled public boolean isRequired { get; set; } @AuraEnabled public boolean isPlaceHolder { get; set; } @AuraEnabled public String defaultValue { get; set; } } public static Integer ControllerUtil() { Integer i = 0; return i; } }