In my experience building AppExchange apps, in particular working on Skuid, which heavily relies on overriding Salesforce Buttons/Actions with Visualforce Pages, you will have to rely on your users creating some local VF Pages within their local orgs --- otherwise, as you have realized, you are in a Catch-22: you'd think you could just include UserInfo.isCurrentUserLicensed(namespace)
in your packaged VF Page controller code to check whether the running user is licensed to use your app.... but this does NOT work because the user will never even make it to this code, unless they are licensed to user your app in the first place!
So, in general, UserInfo.isCurrentUserLicensed(namespace)
should never be used within App1 to determine whether a user is licensed to App1. It CAN be used within local namespace for detecting access to App1, App2, etc., or within App1 for dynamic detection of access to App2, App3, etc. (which is great because this does NOT cause your app to have a dependency on App2, App3, etc.) ---- but obviously that does not help you here.
So, here is the approach I recommend taking:
Provide / help your users create local Visualforce Pages that check whether the running user has a License to your App, then redirects the user to your packaged VF Pages if they do, but otherwise redirects them to the standard Salesforce UI associated with that Button/Action.
I am posting a general purpose Apex Class below called "Redirects" that can be used for this purpose as an extension controller for any VF Page. This allows you to create some very tiny, un-packaged Visualforce Pages which you should be able to provide to your clients (perhaps in an un-managed package that they install directly after install of your base package), which will solve your issue.
//
// APEX CLASS -- to use as Extension Controller
//
public with sharing class Redirects {
public static final string NAMESPACE = 'skuid';
public Redirects(ApexPages.StandardSetController setCtl){}
public Redirects(ApexPages.StandardController ctl) {}
public boolean getLicensed(){
return UserInfo.isCurrentUserLicensed(NAMESPACE);
}
}
//
// TEST CLASS
//
@isTest
private class UnitTests_Redirects {
private static final String NAMESPACE = 'skuid';
private static testMethod void TestStandardController(){
boolean userIsLicensed = UserInfo.isCurrentUserLicensed(NAMESPACE);
Contact c = new Contact(FirstName='James',LastName='Potter');
insert c;
System.assertNotEquals(null,c.Id);
ApexPages.StandardController ctl = new ApexPages.StandardController(c);
Redirects r = new Redirects(ctl);
System.assertEquals(userIsLicensed,r.getLicensed());
}
private static testMethod void TestStandardSetController(){
boolean userIsLicensed = UserInfo.isCurrentUserLicensed(NAMESPACE);
ApexPages.StandardSetController ctl = new ApexPages.StandardSetController(
[select Id,Name from Contact limit 5]
);
Redirects r = new Redirects(ctl);
System.assertEquals(userIsLicensed,r.getLicensed());
}
}
This Redirects class would then be used like so in some super-simple VF Pages that simply send the user off to your desired, packaged VF Pages for the Contact Tab, View, etc. actions, but ONLY if the running user is licensed --- otherwise, you send them to the corresponding standard pages:
//
// Example: VF Page to use for Contact Tab override
//
<apex:page standardController="Contact" recordsetvar="c" extensions="Redirects"
action="{!IF(licensed,
'/apex/skuid__UI?page=ContactTab',
'/003/o?nooverride=1')}"/>
//
// Example: VF Page to use for Contact View override
//
<apex:page standardController="Contact" extensions="Redirects"
action="{!IF(licensed,
'/apex/skuid__UI?page=ContactView&id='+Contact.Id,
URLFOR($Action.Contact.View,Contact.Id,null,true))}"/>
The key thing to note here is the use of the nooverride=1
query string parameter for the NON-licensed Redirect. This is essential, as it forces Salesforce to show the non-overridden , standard Salesforce page for the given action (e.g. Tab, View, Edit). If you leave this off, then you will get stuck in an infinite loop of redirection.
How to use: the first line checks to see if the running user is licensed to your app. THen, it sends the user to appropriate URLs based on whether they are licensed. You should replace everything in the second line of the action
attribute with the URL that you would like to send LICENSED users to, and everything in the third line of the action
attribute with the URL that you want to send UNLICENSED users to.
This general purpose strategy can be used for any standard action override, for any object. Just keep the following in mind:
ACTION ||| NEED TO USE 'recordSetVar="a"'? ||| STANDARD URL FOR OVERRIDE
Tab ||| YES ||| /{keyPrefix}/o
List ||| YES ||| /{keyPrefix}
New ||| NO ||| /{keyPrefix}/e
View ||| NO ||| /{keyPrefix}
Edit ||| NO ||| /{recordId}/e
Clone ||| NO ||| /{recordId}/e?clone=1
Delete ||| NO ||| use URLFOR with $Action
In general, for the unlicensed redirect URL, try to make use of the Visualforce URLFOR()
function in conjunction with $Action.<object>.<actionType>
, making sure to set the 4th parameter to true
so that your user will be sent to the Standard Salesforce page, rather than getting trapped into an infinite loop. Usually the 4th Parameter of URLFOR() just adds nooverride=1
to the redirect URL's query string, which forces Salesforce to show its standard UI page for the given action that you're requesting. However, this syntax isn't consistent, e.g. it doesn't work for $Action.<objectType>.Tab
--- thus I hard-coded the URL for the Tab page example. For more info on the quirks of the URLFOR function, I recommend reading this blog post.
So, I would take the following resources and put them into an un-managed package, which you distribute to your users after they install your managed package:
- Redirects.cls
- UnitTests_Redirects.cls
- ContactTab.page
- ContactView.page
- ContactEdit.page
The user can then easily choose to use these 3 un-managed VF Pages as overrides for the corresponding Contact actions, and doesn't have to deal with writing any Apex in their Sandbox then deploying to Production.