Users in a certain profile (in production) cannot see a new Visualforce page (or rather, a component within the Visualforce page) I've added to the Opportunity layout. The error is
Content cannot be displayed: Invalid parameter for function URLFOR
I've monitored some users with increased log levels. For some reason the status is marked as success, and this is what I get:
11:09:28.107 (107494903)|VF_EVALUATE_FORMULA_BEGIN|06612000000Wiyq|#{URLFOR($Action.Contact.View, relations.Contact.ID)}
11:09:28.108 (108425979)|VF_EVALUATE_FORMULA_END
11:09:28.108 (108787037)|VF_EVALUATE_FORMULA_BEGIN|06612000000Wiyq|#{URLFOR($Action.Account.View, group.ID)}
11:09:28.109 (109584313)|VF_EVALUATE_FORMULA_END
There's also another one that includes jQuery in the Visualforce page. I assume that's not the issue because other profiles are just fine. Given that, what's causing the error? Personally, I think the error is misleading and that there's some sort of permissions issue because the exact same Opportunity is viewed correctly by some profiles.
Latest news: One of my admin users was experimenting, and giving the profile create permissions on Contacts and a custom object (both used by the Visualforce component) and then removing those permissions seems to have fixed the issue. This makes me queasy. Please explain.
Code extracts
Relevant Visualforce component snippet:
<apex:pageBlockTable id="grid" value="{!Relationships}" var="relations" width="100%">
<apex:column headerValue="Contact Name">
<apex:outputLink target="_top"
value="{!URLFOR($Action.Contact.View, relations.Contact.ID)}">
{!relations.Contact.LastName}, {!relations.Contact.FirstName}
</apex:outputLink>
</apex:column>
<apex:column headerValue="Account Name">
<apex:repeat value="{!relations.Groups}" var="group">
<apex:outputLink target="_top"
value="{!URLFOR($Action.Account.View, group.ID)}">
{!group.Name}
</apex:outputLink>
{! IF(group != relations.Groups[relations.Groups.Size - 1], ';', '')}
</apex:repeat>
</apex:column>
</apex:pageBlockTable>
The controller (more or less):
public ID AccountID {
get;
set {
AccountID = value;
if (Relationships == null) Load();
}
}
public List<ContactRelations> Relationships { get; protected set; }
public void Load() {
// Get data
Account a = [SELECT ID, Name, ParentID FROM Account WHERE ID = :AccountID];
ID parentID = a.ParentId == null ? a.ID : a.ParentId;
List<Account> accounts =
[SELECT ID, Name, ParentID, RecordType.Name
FROM Account
WHERE ID = :parentID
OR ParentID = :parentID
ORDER BY Name];
Relationships = new List<ContactRelations>();
for (Contact_Relationships__c crc:
[SELECT
ID,
Related_Contact__r.ID,
Related_Contact__r.Name,
Related_Contact__r.FirstName,
Related_Contact__r.LastName,
Related_Account__r.ID,
Related_Account__r.Name,
Related_Account__r.RecordType.Name,
Relationship_Type__c
FROM Contact_Relationships__c
WHERE Related_Account__c IN :accounts]) {
boolean flag = false;
for (ContactRelations cr: Relationships) {
if (cr.Contact.ID == crc.Related_Contact__c) {
cr.Relationships.add(crc);
flag = true;
break;
}
}
if (!flag) {
ContactRelations cr = new ContactRelations();
cr.Contact = crc.Related_Contact__r;
cr.Relationships.add(crc);
Relationships.add(cr);
}
}
Relationships.sort();
}
And ContactRelations is this class (simplified):
public class ContactRelations {
public Contact Contact { get; set; }
public Set<Contact_Relationships__c> Relationships {
get {
if (Relationships == null) {
Relationships = new Set<Contact_Relationships__c>();
}
return Relationships;
}
set;
}
public List<Account> getGroups() {
Set<Account> subgroups = new Set<Account>();
Account parent;
for (Contact_Relationships__c cr: Relationships) {
if (cr.Related_Account__r.RecordType.Name == 'Group') {
parent = cr.Related_Account__r;
}
else subgroups.add(cr.Related_Account__r);
}
List<Account> groups = new List<Account>();
if (parent != null) groups.add(parent);// List the group first
List<Account> temp = new List<Account>(subgroups);
temp.sort();
groups.addAll(temp);
return groups;
}
}