Problem/Motivation
Currently, the CRM User Contact Mapping system only provides display name override functionality. Site builders need the ability to map additional fields from person contacts to user entities, allowing them to expose CRM contact data through the user entity interface. This would enable:
- Displaying contact information (phone numbers, addresses, dates, etc.) on user profiles
- Allowing users to edit their contact information through their user account forms
- Flexible field configuration per site, choosing which fields to expose and how
- Customization of field widgets and formatters for optimal user experience
Steps to reproduce
N/A - This is a new feature request.
Proposed resolution
Add a field mapping configuration system to the User Contact Mapping settings form that allows site builders to:
- Configure Field Mappings: For each field available on the person contact type, site builders can:
- Enable or disable individual field mappings
- Set a human-readable label/name for the mapped field
- Define a machine name for the exposed field handle (prefixed with "crm__") so it can be referenced consistently in displays
- Choose whether the field should render on user edit forms (system fields such as
id,uuid,created, andchangedare never offered as form options) - Choose whether the field should render on user view pages (system fields can still be exposed here)
- Indicate if the contact field is computed (e.g., the
agefield) so the UI treats it as read-only view-only data - Exclude system-only fields such as
id,uuid,created, andchangedfrom the list of form mapping options so they cannot be mapped
- Field Display Configuration: Site builders can independently control:
- Form display: Which fields appear on user edit forms and which widgets are used
- View display: Which fields appear on user view pages and which formatters are used
- Flexible Field Selection: Site builders can mix and match different fields for different user views, allowing customization per site needs
- Widget and Formatter Selection: Site builders can choose the appropriate field widget (for forms) and formatter (for views) that best works for their particular user display requirements
- Access Control via Plugins: Build a plugin system that governs access control approaches, so site builders can choose or extend how mapped fields behave.
- User Field Plugin: Treats the mapped field like a normal user field, honoring Drupal's native user entity permissions. This is the simplest default plugin.
- Contact Field Plugin: Treats access as though the field belonged to the mapped contact, reusing contact edit permissions.
The plugin system should be extensible so other projects can introduce new access control plugins without modifying the main CRM project. The default plugin selection should be configurable per mapped field, and the default plugin should mimic native user field behavior for simplicity.
Access Control Considerations:
One of the critical design decisions for this feature is how access to mapped fields should be governed. Two approaches are provided by default, with site builders able to choose per field:
- User Entity Permissions (Default):
- Fields are treated as standard user entity fields
- If a user can edit the user entity, they can edit these mapped fields
- Simplest approach, good for fields that users should be able to edit themselves
- No additional permission configuration needed
- Contact Entity Permissions:
- Users must have permission to edit the mapped contact entity to edit these fields
- Provides stricter control, ensuring contact data integrity
- Useful when contact data should only be modified by users with CRM permissions
- Requires checking the user's access to the specific mapped contact
This flexible approach allows site builders to choose the appropriate access control method based on their security requirements and use case. The plugin system is extensible, allowing other modules to provide additional access control strategies for specific use cases. The default should be "User Entity Permissions" to maintain simplicity and backward compatibility.
Technical Implementation:
- Add global
enable_mapped_fields_on_formsetting to show or hide mapped fields on user forms - Extend the User Contact Mapping settings form to include a field mapping configuration section
- Dynamically discover all fields available on the person contact type
- Define computed field wrappers that expose the contact fields on the user (using the configured "crm__" handles)
- Implement field value computation: values are always computed on-the-fly from the mapped contact entity with no caching (Drupal's entity cache will handle performance)
- Implement form submission handler that:
- Loads the mapped contact entity for the user
- Compares submitted field values with current contact field values
- Only updates and saves the contact entity if any mapped field values have changed
- Handles validation and error reporting for contact field updates
- Integrate with Drupal's Field UI to allow widget/formatter configuration
- Implement access control service to check field edit permissions based on configured method
- Ensure computed fields (like
age) can be mapped, but treat them as read-only so they only appear in view displays and skip form integration
Remaining tasks
- ✅ Design and implement the field mapping configuration UI in the settings form
- ✅ Implement field discovery for person contact type fields
- ✅ Define computed field metadata/wrappers for each mapped contact field
- ✅ Implement field value computation logic that always reads from the mapped contact (no caching layer needed)
- ✅ Add form display integration (hook_form_user_form_alter enhancement) that renders the computed fields
- ✅ Add form submission handler that:
- Detects changes to mapped contact fields
- Loads the user's mapped contact entity
- Updates only changed values on the contact
- Saves the contact entity when necessary
- ✅ Add view display integration (hook_entity_view_alter or similar) to render the computed fields
- ✅ Add validation to ensure machine names are valid and unique
- ✅ Add configuration schema for field mappings
- ✅ Implement plugin-based access control system for mapped fields:
- Create a plugin manager plus plugin interfaces for access control
- Provide default plugins for user-based access and contact-based access
- Allow third-party modules to register additional access control plugins
- Add access checks to form and view renderings that delegate to the selected plugin
- ✅ Ensure the configuration/form builder never exposes
id,uuid,created, orchangedas form-display options while still allowing view exposure - ✅ Write tests for field mapping functionality
- ✅ Write tests for access control scenarios
- ✅ Update documentation (docs restructured to
docs/user-mapping/with entity.md, field-mapping.md, event.md, index.md) - ✅ Post-update hook
crm_post_update_add_enable_mapped_fields_on_form_settingfor existing installs
User interface changes
- Menu structure: "User Contact Mappings" now points to the settings form (
crm.user_contact_mapping.settings); the entity list is a secondary tab at/admin/config/crm/user/list - Settings Form Enhancement: Add a new "Field Mapping" section to
/admin/config/crm/user/settingsthat includes:- Global "Show mapped fields on user forms" checkbox (
enable_mapped_fields_on_form) to enable/disable mapped fields on user registration and profile forms - A table or list of all person contact fields
- For each field: Enabled checkbox, Label input, Machine name input, Form display checkbox, View display checkbox, Access control plugin selector (User Entity Access / Contact Entity Access)
- Validation and help text for machine name format
- Help text explaining each access control option
- Exclude system-only fields such as
id,uuid,created, andchangedfrom form mapping options while still allowing them to be exposed on user views if desired
- Global "Show mapped fields on user forms" checkbox (
- User Edit Forms: Mapped fields will appear in the CRM section (or appropriate location) on user edit forms when form display is enabled; computed fields will be excluded from forms because they cannot be edited directly.
- User View Pages: Mapped fields will appear on user view pages when view display is enabled; computed fields (like
age) are treated as read-only view-only fields - Field UI Integration: Mapped fields will be available in Field UI for widget and formatter configuration at:
/admin/config/people/accounts/form-display(for form widgets)/admin/config/people/accounts/display(for view formatters)
API changes
- UserFieldMappingService (
crm.user_field_mapping): Handles field discovery for person contact types, mapping retrieval, and mapped contact lookup - UserContactDisplaySyncService (
crm.user_contact_display_sync): Syncs field display configuration between contact and user entity displays - UserContactFieldValuesStorage (
crm.user_contact_field_values_storage): Handles reading/writing field values between user forms and the mapped contact entity - UserFieldAccessManager (
plugin.manager.crm_user_field_access): Plugin manager for access control strategies; plugins use#[UserFieldAccess]PHP attribute for discovery - Computed field item lists:
MappedContactFieldItemListfor standard fields;MappedEntityReferenceFieldItemListfor entity reference fields (emails, addresses, telephones) - UserContactMappingSettingsConfigSubscriber: Event subscriber that syncs Field UI display config when field mappings change
- Configuration Schema: Extended
crm.user_contact_mapping.settingswithcrm.user_field_mappingfor field mapping configuration - Form Alter Hook Enhancement: Extend
hook_form_user_form_alterimplementation to:- Add mapped fields to user forms with proper access checks
- Add custom form submission handler that detects changes and updates the contact entity
- View Alter Hook: Implement hook to add mapped fields to user view displays with proper access checks
- Access Control Plugin Manager: Plugin manager (
plugin.manager.crm_user_field_access) handles field access checks:- Plugins discovered via
#[UserFieldAccess]attribute withid,label,description - Default plugins:
user_entity(UserEntityFieldAccess),contact_entity(ContactEntityFieldAccess) - Third-party modules can add plugins by extending
UserFieldAccessBase - Return access results and caching hints for form/view hooks
- Plugins discovered via
- Field Access Hook: Implement
hook_entity_field_accessor use field access handlers to enforce access control on mapped fields
Data model changes
- Configuration Storage: Store field mapping configuration in
crm.user_contact_mapping.settingsconfig entity with structure:enable_mapped_fields_on_form: Global boolean to show mapped fields on user registration and profile forms (default:true)- Field mappings array containing:
enabled,contact_field_name,user_field_label,user_field_machine_name,form_display,view_display,access_control_plugin - Access control plugin IDs:
user_entityorcontact_entity(third-party modules can register additional plugins via#[UserFieldAccess]attribute)
- Exposed Field Handles: Computed wrappers expose contact data on the user. Their machine names use the
crm__{configured_machine_name}convention, but no additional field storage is added to the user tables. - Field Storage: Data originates from CRM contact fields and lives in the contact tables; the user-side exposures compute values on-the-fly from the mapped contact entity. No data duplication occurs.
- Value Computation: All field values are computed on demand from the mapped contact entity each time they are accessed. Drupal's entity caching layer handles performance optimization.
- Form Submission Flow: When user forms are submitted, mapped field values are compared against the current contact field values. Only if changes are detected is the contact entity loaded, updated, and saved.
- Computed Fields: Computed values (like
age, derived fromstart_date/end_date) are stored only in the contact system. They can be mapped as view-only fields on the user but not rendered on forms or stored separately. - No Schema Changes: No direct database schema changes needed - uses Drupal's standard field storage system for contact entities only
| Comment | File | Size | Author |
|---|
Issue fork crm-3568006
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
svendecabooterAdding related issue in primary_entity_reference, when widgets by that module are used on the the user registration form.
Comment #4
svendecabooterAdd related issue to use #config_target, which, if committed first, would trigger updates in the MR of this issue.
Comment #5
svendecabooterThe MR is now ready for review:
- Go to
/admin/config/crm/user/settings- Enable "Include Person contact form in user registration" option
- Optionally configure and set a different form mode for the Contact entity (Person bundle), to show less fields on the user registration form.
- Log out and go to
/user/register- Create an account and fill in the extra Contact entity form fields
- Log back in as admin, and check the newly created Contact entity, and its saved fields.
Comment #8
anjuelsaphilip2020 commentedHi, I’ve verified and tested MR !175 and successfully applied the patch on CRM 1.0.x-dev. The changes are working as expected.
Following steps I followed:
Testing Result: After applying the patch, Person contact fields show on the registration form, and a Person contact is created when a user registers.
Attaching screenshots for reference.
Thanks
Comment #9
bluegeek9 commentedI appreciate you taking the initiative on this. I want to go in a slightly different direction. I want to expose the contact field onto the mapped user. I update the issue summary with roughly how I think it can be implemented, but it should not be treated as gospel.
Comment #11
svendecabooterComment #13
svendecabooterThanks for the feedback. The alternative approach seems like a good way to go about this.
I have now created a new branch and (draft) MR to implement this.
Still todo:- Add support for primary_entity_reference fields, since they seem to pose problems currently
- Add functional tests
Comment #14
svendecabooterTests seem to fail because of #3570610: show_primary_only schema missing
Comment #15
svendecabooterI think this is ready for review
Comment #16
bluegeek9 commentedI have done an initial review. You did good work.
It is a large MR just by the nature of the issue.
I am preparing to present CRM to FL Drupal Camp, so the MR may not be merged immediately.
I encourage others to review the MR.
Comment #17
jdleonardThis is impressive and will be incredibly powerful.
I proposed some changes in a somewhat superficial (code-only) review of the MR. I have not yet played with the feature.
An issue summary update of remaining tasks would also be helpful.
Comment #18
svendecabooterComment #19
bluegeek9 commentedComputed fields are on the form display. The same is true for id and other internal fields
https://mr187-rlkfw4mjuusafstk8z8ghvrbnamrvb75.tugboatqa.com/admin/confi...
Add the age field to the view display. The settings form is correct, only the view checkbox is enabled.
https://mr187-rlkfw4mjuusafstk8z8ghvrbnamrvb75.tugboatqa.com/admin/confi...
Go to the User form display. You see both fields.
========================
When adding fields to the user in this way, should we check each contact display against each user display? If the person has the same display name as the user, we copy the field settings. If there is not a matching display the field is hidden. I am not suggesting the two be linked in some way, only which default settings to use when adding these types of fields.
Comment #20
svendecabooterThe issue with computed and system fields being form configurable should be fixed.
Using the same field settings on the user entity, as currently set on the contact entity, is probably a good idea.
Maybe we can add that in a followup finetuning issue? Or do we want this issue to cover the full scope?
Comment #21
svendecabooterComment #22
svendecabooterPotential follow-up issues raised in this one - please let me know if they should be handled within this issue anyway:
Comment #23
svendecabooterI have addressed all feedback in the MR and this ticket.
I have added remarks where I thought no changes were needed. Please discuss if you do not agree.
Comment #24
jdleonardI agree with all of your responses Sven, thank you! Sorry for the noise on the return type definitions; I thought I had run into trouble there previously...
I think both potential follow up issues are suitable for follow up issues.
It'll be a while until I can properly review this feature, but it needn't wait on me.
Comment #25
bluegeek9 commentedI changed the behavior from neutral to forbidden when the plugin is not found or throws an exception.
I also changed == to ===
Comment #26
bluegeek9 commentedI split the user mapping documentation into multiple pages.
Comment #27
bluegeek9 commentedComment #28
bluegeek9 commentedI am happy with everything except the description text on the setting page, which can be changed later.
Comment #29
bluegeek9 commentedComment #33
svendecabooterWhat's the additional "Show mapped fields on user forms" boolean field for?
Each mapped field already has the "Form" checkbox which takes care of that, doesn't it?
It's not clear to me how these relate to each other.
Comment #34
jdleonardI don't think this was necessarily the intent, but I created #3575071: Separate form for field mapping to make better use of this checkbox.