CRM-8356 Limit contact reference custom fields by group with advanced mode to use other filters

    Details

    • Type: New Feature
    • Status: Done/Fixed
    • Priority: Major
    • Resolution: Fixed/Completed
    • Affects Version/s: 4.1.0
    • Fix Version/s: 4.1.0
    • Component/s: Core CiviCRM
    • Labels:
      None

      Description

      The 'basic' feature is to allow administrators to select a specific group of contacts to be available for a given contact reference custom field when the field is created or edited (i.e. contact search / list widget would expose only contacts in the selected group). An 'Advanced' filter allows administrators to specify more complex filters using available parameters to API v3 Contact Get api.

      In order to support including contact reference fields in front-end forms (Profiles, and profiles w/in online contribution pages and event registration pages) - access to this type of field will require EITHER 'access CiviCRM' permission (useful for back-office forms) OR 'access contact reference field' permissions (this permission should be enabled for anonymous users if contact reference fields are to be embedded in front-end forms not requiring login.

      A new Search Preference will be added to control the contact data included in the widget's search results dropdown. This setting is configurable from Administer > Customize > Search Preferences. It is separate from the existing Autocomplete Contact Search setting (which controls data included in Quick Search widget results).

      The widget will support two contact API actions:
      'get' - the existing API action, with any currently available parameters
      'lookup' - a new contact API action which is a wrapper for the existing CRM_Contact_Page_AJAX::getContactList method with support for group filtering and appropriate permissioning for front-end usage (details below)

      BASIC EXAMPLE: For a basic 'group filter' (e.g. only expose contacts in group_id 3), user would select from a muli-select listing of groups. If they select "Board of Directors" (group_id = 3), we would store: 'action=lookup&group=3' in the filter column. The PHP function invoked by the widget will call the contact get API with that filter parameter: entity=Contact&action=lookup&group=3. Multiple group selection is supported (&group=3,5).

      ADVANCED EXAMPLE: Administrator wants to pass additional / alternative filters (as supported by Contact Get API). To expose all 'Students' in group 3, user will click 'Advanced' link which replaces group dropdown with a text input field. They would enter the following value: "action=get&group=3&contact_sub_type=student". The PHP function invoked by the widget will invoke the contact get api with that filter parameter: entity=Contact&action=get&group=3&contact_sub_type=student

      NOTE: The api entity (contact) will be hard-coded in the PHP class to simplify error checking and debugging. The widget and enclosing forms do not support other objects (e.g. Activity). A formRule will be used to verify that only valid actions are saved to the filter ('get' or 'lookup'). Other operations (e.g. create) are not supported. The form's preprocess can also easily distinguish between BASIC and ADVANCED mode field display by splitting DB value on & delimiter and checking for values other than / in addition to "action=lookup&group=$listOfINTs".

      ==============
      DB Changes
      ==============
      Add column to the custom field table to store the contact reference list 'filter'. For upgrades:

      • ALTER TABLE `civicrm_custom_field` ADD `filter` VARCHAR 255 NULL COMMENT `Stores Contact Get API params contact reference custom fields. May be used for other filters in the future.`

      Filter column will store & delimited params to CONTACT GET api.

      ============
      Implementation
      ============
      1. Add Custom Field
      1.1 When Data Type = Contact Reference is selected, show a new field for the filter.
      Label = 'Limit List to Group(s)'
      Field Type = Multi-select with all groups. Preferable use the crmasmSelect widget (as used in Advanced Search). Else the TagsAndGroups style table of checkboxes. Include all groups (regardless of group type) in alpha order by group title.
      NOTE: For checkbox / item values - could use just group_id's OR use the corresponding filter strings ("group_id=3") which is what we will be storing in the DB.

      1.2 Include link to right of dropdown. Link text = 'Advanced Filter'. When link is clicked, replace the row with the label and dropdown (above) with:
      Label = 'Advanced Filter'
      Text input field (class=huge)
      <span class="description">Filter contact listing using Contact Get API parameters. EXAMPLE: To list only Organizations in group 3: "action=lookup&group=3&contact_type=Organization"

      {docURL page="CiviCRM Public APIs"}

      </span>
      Include link to right of input field. Link text = 'Filter by Group'. Clicking this link hides the advanced filter label / field and shows the basic 'Limit List to Group' filter. If the advanced filter field contains only a group=N filter, we can select (check) that group when we switch back. Otherwise, no group is checked.

      1.3 FormRules

      • Limit List to Group my be empty (in this case all contacts in the DB are exposed unless a hook is called)
      • Advanced Filter field - Check for 'entity=' and for action != 'lookup' AND action !='get'. "Please do not include entity parameter (entity is always 'contact')." "Only 'get' and 'lookup' actions are supported."

      1.4 PostProcess
      Save field value to filter column.

      2. Edit Custom Field Preprocess needs to determine if this contact reference field is using 'basic' or 'advanced' mode and display the appropriate field. If the filter field value is anything other than empty OR 'action=lookup&group=' . $listOfINTs, then display advanced filter.

      3. Implement 'lookup' action for contact API:

      • Wrapper for CRM_Contact_Page_AJAX::getContactList (continue to return contact_autocomplete_options as as configured)
      • Adds filter by group(s) when group_id parameter is passed
      • Permissioning: lookup action requires 'profile listings' OR 'profile listings and forms' OR 'view all contacts' (or any higher permission that implies view all contacts - not sure about this ??).

      4. Modify contact reference implementation of autocomplete widget to invoke the appropriate API (lookup or get) and pass parameters if provided when context=customfield. If no filter, then all contacts should be returned (current behavior) if user has applicable permissions.

      NOTE: If we get invalid filter parameters in advanced mode (i.e. the API is returning an error) would be great if we could pass some error string back to the widget in a way that it displays to the user - rather than just spinning or ...

      5. Contact reference widget should be rendered by a separate .tpl file (ContactReference.tpl) that is included for custom contact reference field editing (contact form and profiles) - similar to how we've isolated Tag and Group editing (CRM/Contact/Form/Edit/TagsAndGroups.tpl).

        Attachments

          Activity

          [CRM-8356] Limit contact reference custom fields by group with advanced mode to use other filters
          David Greenberg added a comment -

          Currently it doesn't look like group_id is supported as a parameter for Contact Get api. I've sent inquiry to Xavier / API team on this.

          David Greenberg added a comment -

          The filtering isn't working for me. I checked the code in CRM_Contact_Page_AJAX::getContactListCustomField - and it doesn't look like the filter parameters stored in the custom field are being passed in $params to the api get call. In fact I don't see any code to retrieve the filter parameters stored in the associated custom field "filter" column ???

          EXAMPLE: Create a Contact Reference field, select Limit Listing to Group : Administrators, Advisory Board. The 'filter' value in the DB is correct:
          action=lookup&group=1,4

          When I edit a contact with this field, the auto-complete is returning ALL contacts, not just contacts in groups 1 and 4.
          The URL being invoked by the field is:
          http://civicrm40/civicrm/ajax/rest?className=CRM_Contact_Page_AJAX&fnName=getContactList&json=1&reset=1&context=customfield&id=7&s=c&limit=10&timestamp=1321645109325

          ... but the $params passed here:
          $contact = civicrm_api('Contact', 'Get', $params);

          ... don't include the filter:
          $params

          Array
          (
          [offset] => 0
          [rowCount] => 10
          [version] => 3
          [return.sort_name] => 1
          [return.email] => 1
          [sort_name] => c
          )

          Rajan P Mayekar added a comment -

          Issue was working fine before code merge in rev 35903.
          (https://fisheye2.atlassian.com/browse/CiviCRM/trunk/CRM/Core/BAO/CustomField.php?r1=35731&r2=35903)
          I have fixed the code accordingly, assigning for QA.

          David Greenberg added a comment -

          Rajan - Filtering is working properly now, thanks.

          However, the permissioning on the "lookup" action isn't working as spec'd. Item, Item 3.3 says:
          "- Permissioning: lookup action requires 'profile listings' OR 'profile listings and forms' OR 'view all contacts' (or any higher permission that implies view all contacts"

          This allows admins to use a filtered contact reference field in a profile exposed to anonymous users - which is what we need - IF the anonymous role has profile listings or profile listings and forms. Currently, if you include a contact reference field in a profile (create) and view that form as anonymous, no search results are returned.

          Rajan P Mayekar added a comment -

          I looked at the code it looks fine (CRM/Page/AJAX.php L126 - L139), and it is respecting above permissions (ie 'profile listings' OR 'profile listings and forms' OR 'view all contacts' OR 'edit all contacts' )

          I think issue is related to
          http://issues.civicrm.org/jira/browse/CRM-8840
          ie:
          <item>
          <path>civicrm/ajax/rest</path>
          <page_callback>CRM_Utils_REST::ajax</page_callback>
          <access_arguments>access CiviCRM,access AJAX API</access_arguments>
          </item>

          So to access 'ajax rest' anonymous users should have 'access CiviCRM' and 'access AJAX API' permissions?

          David Greenberg added a comment -

          Kurund - I went over this w/ Lobo and he wants the implementation modified so that it uses it's own URL which is specific to contact reference fields (rather than civicrm/ajax/rest). Do check w/ him on details, but some of the changes needed are:

          1. add 'access contact reference fields' permission, and require that permission for the new URL (since currently ajax/rest requires 'access CiviCRM,access AJAX API')

          2. don't pass the group= filter (or an filters) in the URL - since the filter(s) can be retrieved in the getContactListCustomField() function from the DB (since we know the custom field ID)

          We also want to add a separate setting to be used here rather than 'contact_autocomplete_options' (same concept, but allows admin to set different return properties for contact reference will often be used for public rather than staff forms). I can do that part once the other pieces are in place.

          Kurund Jalmi added a comment -

          Fixed #1 and #2

          David Greenberg added a comment -

          This functionality is feature complete in Alpha 1 - but the contact_reference_options option group was not inserted in the alpha 1 upgrade so it will not function properly for sites upgraded to alpha 1. This will be addressed in the alpha2 upgrade.

          David Greenberg added a comment -

          Xavier - Please do a quick review of the implementation to confirm that it meets the use cases you had in mind when suggesting the api-driven implementation.

          xavier dutoit added a comment -

          Hi,

          Not sure what Dave wanted me to do, but looking at the (long list of comments, I saw a "moved out of the ajax/rest" api path. Didn't understand Lobo's rationale (nor what was the problem in the first place of using it), but he made that choice and that's too late to understand/discuss it in the process, so closing the issue

            People

            • Assignee:
              xavier dutoit
              Reporter:
              Kurund Jalmi

              Dates

              • Created:
                Updated:
                Resolved: