I implemented user preferences for a Visualforce tool using custom metadata type record to save the preferences. Although the VF controller is a "with sharing" class, it calls the (metadata api based) record update method in a separate "without sharing" class. I used custom metadata type instead of custom settings due to Salesforce's "strong recommendation" to do so.
Although it works for system administrator, for a standard user I am getting this error in deployment status: "insufficient access rights on cross-reference id". There error disappears if I assign that profile the "Modify Metadata Through Metadata API Functions" permission (which requires "View Setup and Configuration").
I thought that abstracting out to a class without sharing (which only allows specific custom metadata types) would let me do this, but unfortunately it is not working. I may have to try to reimplement using custom settings instead of custom metadata types, but still don't know if the same issue will arise. I guess I could use a custom object.
Is there any way to get this to work for standard user? If not with custom metadata types, then should it work with custom settings? Or should I just use a custom object?
p.s. This page shows someone using a proxy class to resolve a similar issue for using custom settings from Visualforce it seems. But the answer is from 2014 and it seems silly that it would work so I am not going to depend on it as a guide to using custom settings. At this point guessing a simple custom object is the only solution..
global with sharing class MyVFCustomController {
public void savesettings() {
...
public UserSettingsMetadata meta = new publicUserSettingsMetadata();
meta.upsertMetadataRecord(objname,recname,prefsmap);
}
}
public without sharing class publicUserSettingsMetadata {
/*
Allow a user to set his own user preferences using metadata api
even if the user does not have metadata api / modify all permissions,
but limit the custom metadata types accessible in this way.
Used by Visualforce User Settings pages.
*/
private static Set<String> allowedPublicSettingTypes = new Set<String>{
'Global_Meeting_Report_User_Setting__mdt'
};// list of custom metadata types allowed public access
public publicUserSettingsMetadata() {
}
public void upsertMetadataRecord(String objname, String label, Map<String, Object> fieldWithValuesMap) {
// Insert or overwrite user settings record based on unique metadata fullname
// Usage: upsertMetadataRecord(fullname,label,prefsmap);//fullname = label = 'GlobalMtgReportUser_' + userid
// fullname example:
// Global_Meeting_Report_User_Setting__mdt.GlobalMtgReportUser_0056F0000072VjyQAE
if ((String.isBlank(objname)) || String.isBlank(label)) {return;}
if (allowedPublicSettingTypes.contains(objname) == false) {return;}
Metadata.CustomMetadata customMetadata = new Metadata.CustomMetadata();
customMetadata.fullName = objname + '.' + label;
customMetadata.label = label;
for (String key: fieldWithValuesMap.keySet()) {
Metadata.CustomMetadataValue customField = new Metadata.CustomMetadataValue();
customField.field = key;
customField.value = fieldWithValuesMap.get(key);
customMetadata.values.add(customField);
}
Metadata.DeployContainer mdContainer = new Metadata.DeployContainer();
mdContainer.addMetadata(customMetadata);
PublicSettingsCallback callback = new PublicSettingsCallback();
if (Test.isRunningTest() == false) {
Id jobId = Metadata.Operations.enqueueDeployment(mdContainer, callback);
System.debug('Metadata enqueued jobId: '+jobId);
}
}
}
public class PublicSettingsCallback implements Metadata.DeployCallback {
public PublicSettingsCallback() {}
public void handleResult(Metadata.DeployResult result,
Metadata.DeployCallbackContext context) {
Id jobId = context.getCallbackJobId();
System.debug(result.status);
}