LH Customer Relationship Management
Do contact forms the LocalHero way
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/lhero-org/lh-crm/archive/refs/heads/main.zipA contact form and enquiry management plugin for LocalHero WordPress Multisite deployments.
Description
LH CRM provides contact forms and enquiry tracking for community organisations running the LocalHero platform. It integrates deeply with BuddyPress for user provisioning, group management, in-app notifications, and activity streams. Task management has moved to the standalone lh-tasks plugin.
Installation
- Upload the
lh-crmfolder to/wp-content/plugins/ - Network-activate the plugin through the Network Admin Plugins screen
- Ensure
lh-user-provisioningis also network-activated for full user creation support
Requirements
- WordPress 6.0+
- PHP 8.0+
- BuddyPress (network-activated)
- LH User Provisioning (recommended)
Directory Structure
lh-crm/
├── lh-crm.php Main plugin file (bootstrap only)
├── assets/scripts/ Frontend and editor JavaScript
├── includes/ CRM-specific classes
├── languages/ Translation files
├── libraries/ Drop-in utility classes (no CRM dependency)
└── partials/ PHP view partials
Admin Interface
Contact Forms (lh-crm-form CPT)
Registered under the Contact Forms menu. Each form post configures:
| Meta key | Description |
|---|---|
_lh_crm-fe_full_name-hide |
Hide the full name field on the frontend form |
_lh_crm-fe_message-hide |
Hide the message field on the frontend form |
_lh_crm-form_submit_button |
Submit button label |
_lh_crm-field_group |
BuddyPress xProfile field group ID to display on the form |
_lh_crm-default_category |
Default enquiry category term ID applied to new enquiries |
_lh_crm-recipient_id |
User ID of the staff member who receives enquiries (required) |
_lh_crm-group_id |
BuddyPress group ID to invite/add the submitter to |
_lh_crm-group_action |
Group action: add_to_group, email_invite, or silent_invite |
_lh_crm-bp_notification_recipients |
Array of user IDs to receive BP in-app notifications |
_lh_crm-notification_id |
Post ID of the bp-email notification template |
_lh_crm-response_id |
Post ID of the form response template shown after submission |
Enquiries (lh_crm_enquiry_post CPT)
Enquiries are created on frontend form submission. Custom statuses:
| Status | Description |
|---|---|
lh_crm-new_enq |
Newly received enquiry |
lh_crm-prog_enq |
In progress |
lh_crm-pending_enq |
Pending further action |
lh_crm-res_enq |
Resolved |
lh_crm-clos_enq |
Closed |
Enquiry meta:
| Meta key | Description |
|---|---|
_lh_crm-reference |
Deduplication key generated client-side |
_lh_crm-page_id |
Post ID of the page the form was submitted from |
_lh_crm-referrer |
HTTP referrer URL at time of submission |
_lh_crm-enquiry_notes |
Staff notes (admin only) |
_lh_crm-status-{slug}-at |
UTC timestamp of most recent transition to each status |
Hooks and Filters
Actions
| Hook | Arguments | Description |
|---|---|---|
lh_crm_enquiry_created |
$post_id, $form_id, $user |
Fired after a new enquiry post is created |
lh_crm_http_post_after_user |
$user |
Fired after user resolution during form submission |
Filters
| Hook | Arguments | Description |
|---|---|---|
lh_crm_message_title |
$title, $user |
Filter the enquiry post title |
lh_crm_message_content |
$content, $user |
Filter the enquiry post content |
lh_crm_user_summary |
$content, $user |
Filter the user summary token used in notification templates |
lh_crm_enquiry_action |
$action, $page_id, $user_id |
Filter the enquiry action string |
lh_crm_send_notification_tokens |
$tokens, $user |
Filter the token array used in notification email templates |
lh_crm-form-after_full_name |
$html, $atts |
Filter form HTML after the full name field |
lh_crm-form-after_frontend_email |
$html, $atts |
Filter form HTML after the email field |
lh_crm-form-after_frontend_subject |
$html, $atts |
Filter form HTML after the subject field |
lh_crm-form-after_frontend_submit |
$html, $atts |
Filter form HTML after the submit button |
Shortcode
[lh_crm_form postid="123" subject_type="hidden" subject_value="Enquiry" message_placeholder="Your message"]
| Attribute | Default | Description |
|---|---|---|
postid |
— | Post ID of the lh-crm-form post (required) |
subject_type |
text |
Input type for subject field: text or hidden |
subject_value |
— | Default value for the subject field |
message_placeholder |
Your Message |
Placeholder text for the message textarea |
action_url |
current URL | Form action URL |
Also available as a Gutenberg block: lh-crm/form.
Cron Jobs
| Hook | Schedule | Description |
|---|---|---|
lh_crm_initial_run |
Once on activation | Runs initial capability setup |
lh_crm_wakeup |
Hourly | Activates future-dated enquiries; triggers relationship resync |
Database Tables
No custom tables. All data stored in standard WordPress posts, postmeta, and term_relationships tables. Relationship graph triples written to lh_relationships via LH_Relationships_Store if lh-relationships is active.
Constants
None. Version is accessed via LH_Crm_plugin::plugin_version().
Changelog
1.5.0 — 2026-07-05
- Removed the
lh_crm-task_postCPT and its admin UI in favour of the standalonelh-tasksplugin, now the shared task queue used across the plugin suite. Existing task records were migrated by hand prior to this release. - Removed the stale
lh_crm-task_postreference from the commented-out destructive cleanup block inuninstall.php
1.4.0 — 2026-06-21
- Refactored monolithic main file into separate include classes:
LH_Crm_form_submission_class,LH_Crm_notifications_class,LH_Crm_relationships_class,LH_Crm_activity_class,LH_Crm_mcp_abilities_class - Added
lh-crm/query-enquiriesMCP ability with filtering by status, form, author, category, search, and site - Moved drop-in utility classes to
libraries/directory - Consolidated two
partials/directories into single rootpartials/ - Added
languages/directory,readme.md, correctedreadme.txt(was a copy from an unrelated plugin) - Added
uninstall.php - Added
register_post_meta()calls to enquiry and contact form post type classes - Registered post meta for all enquiry and contact form meta keys
- Added
plugin_version()identity method - Added
ensure_user_is_added()legacy fallback for when lh-user-provisioning is absent - Added
apply_filters('lh_crm_init_plugin', true)gate toplugins_init() - Fixed menu label from "Contact Forms" to "CRM"
- Fixed dead
_lh_crm-request_emailpost meta fallback removed fromshow_column_content() - Removed dead
ensure_user_login()andget_role_names()methods - Removed empty
LH_Crm_user_flags_taxonomy_classstub - Removed completed
migrate_enquiry_activity()migration function - Replaced direct BP activity DB query in
get_ids_of_non_site_activities()withbp_activity_get()+filter_query - Fixed 75 i18n calls incorrectly using
return_plugin_namespace()as text domain — replaced withreturn_plugin_text_domain() - Fixed
wp_unslash()missing on$_POSTreads throughout form submission and metabox save handlers - Fixed missing ABSPATH guards in partials and library files
- Fixed unescaped output on
get_edit_post_link,bp_members_get_user_url,add_query_arg,get_permalinkin admin columns - Fixed
_e()→esc_html_e()in task and contact form classes - Fixed
wp_die()output now usesesc_html__() - Fixed ordered
%1$s/%2$splaceholders and added translators comments inadd_enquiry() - Fixed stale include path for
lh-register-file-class.phpafter move tolibraries/ - Fixed
suppress_filters→ accepted false positive (intentional, filters this plugin's admin dropdowns) - Bumped
Requires at leastto6.9for abilities API; addedLicenseheader publickeyword on instance properties (wasvar)
1.32 — 2026-06-21
- Refactored monolithic main file into separate include classes:
LH_Crm_form_submission_class,LH_Crm_notifications_class,LH_Crm_relationships_class,LH_Crm_activity_class - Moved drop-in utility classes (
LH_Post_ip_address_taxonoy_class,LH_Post_user_agent_class,LH_Register_file_class) fromincludes/tolibraries/ - Consolidated two
partials/directories into single rootpartials/ - Removed dead
_lh_crm-request_emailpost meta fallback fromshow_column_content()— value was never written anywhere - Added
languages/directory