This article documents the Contacts module of the WPResidence real estate CRM plugin from a code perspective: storage, CRUD API, AJAX endpoints, field map, hooks, and permissions.
Storage – wp_wpresidence_crm_contacts
Contacts live in a dedicated custom table with 44 columns, created in libs/db-setup.php. No CPT is used for new records. The legacy wpestate_crm_contact post type is kept only for backward compatibility and is migrated on activation.
Key Columns
| Column | Purpose |
|---|---|
| contact_id | Primary key, auto-increment |
| first_name, last_name, prefix | Personal info |
| email, mobile, phone, home_phone | Contact methods; email is the duplicate-detection key |
| address, city, county, state, zipcode, country | Address |
| lifecycle_stage | Default new_lead |
| lifecycle_stage_changed_at | Timestamp used by time-based automations |
| lead_source, lead_type | Marketing attribution |
| status | Active / Inactive / VIP / Blocked |
| assigned_agent_id | WP user ID of the owning agent |
| user_id | WP user ID of the creator |
| wp_user_id | WP user ID if the contact has a WP login |
| tags | Comma-separated string |
| private_note | Internal note |
| pref_min_price, pref_max_price, pref_min_beds, pref_max_beds, pref_min_baths, pref_max_baths, pref_min_area, pref_max_area, pref_locations, pref_property_type, pref_property_status | Buyer preferences used by the matching engine |
| twitter, linkedin, facebook | Social URLs |
| created_at, updated_at | Timestamps |
Indexes
On email, user_id, status, lifecycle_stage, assigned_agent_id, lead_source.
CRUD API – libs/contact-crud.php
| Function | Purpose |
|---|---|
| wpestate_crm_insert_contact($data) | Insert; returns contact_id or WP_Error |
| wpestate_crm_get_contact($contact_id) | Fetch one row |
| wpestate_crm_get_contacts($args) | Fetch many; supports status, lifecycle_stage, lead_source, lead_type, tags, assigned_agent_id, date range, search, orderby, limit, offset |
| wpestate_crm_update_contact($id, $data) | Update; fires wpestate_crm_after_update_contact |
| wpestate_crm_delete_contact($id) | Delete row and cascade clean up |
| wpestate_crm_bulk_delete_contacts($ids) | Bulk delete |
| wpestate_crm_find_contact_by_email($email) | Duplicate-detection helper |
| wpestate_crm_contact_field_map() | Maps form keys (e.g., crm_first_name) to DB columns |
Page and Form Templates
- page-templates/wpestate-crm-dashboard_contacts.php – list view with 9 filters.
- templates/crm_contact_detail.php – detail page.
- templates/crm_add_contact.php – add/edit form.
- templates/dashboard_contact_unit.php – single contact row/card.
AJAX Endpoints
| Action | Nonce |
|---|---|
| wpestate_crm_save_contact | wpestate_crm_save_contact |
| wpestate_crm_delete_contact | wpestate_crm_delete_contact |
| wpestate_crm_bulk_delete_contacts | wpestate_crm_bulk_delete_contacts |
| wpestate_crm_bulk_update_contacts | wpestate_crm_bulk_update_contacts |
| wpestate_crm_search_contacts | wpestate_crm_search_contacts |
Hooks
- wpestate_crm_after_insert_contact – fired after a row is inserted. Used by notifications, webhooks, HubSpot sync, automations.
- wpestate_crm_after_update_contact – fired after update; includes lifecycle stage change detection.
- wpestate_crm_after_delete_contact – fired after delete.
Permissions
- Capability: manage_crm (baseline) plus wpestate_crm_user_can(‘contacts’, $action).
- Scope: wpestate_crm_get_ownership_where() returns 1=1 for admins and (user_id = %d OR assigned_agent_id = %d) for agents.
- Per-row guard: wpestate_crm_user_owns_contact($id) is used in mutation AJAX handlers.
Default Picklists
Defined in libs/settings.php via wpestate_crm_get_setting():
- crm_contact_statuses – Active, Inactive, VIP, Blocked
- crm_lifecycle_stages – New Lead, Contacted, Appointment Set, Active Client, Under Contract, Closed
- crm_contact_prefixes – Mr, Mrs, Ms, Dr, Prof
- crm_lead_sources – 10 defaults including Website, Referral, Social Media, Portal, Zillow, Realtor.com
- crm_lead_types – Buyer, Seller, Tenant, Landlord, Investor, Other
Auto-Creation from Forms
The libs/auto-create.php module calls wpestate_crm_insert_contact() for every qualifying theme form submission. Duplicate detection is done by email. If matched, the code updates the existing contact instead of creating a new one.
HubSpot Field Mapping
- first_name → firstname
- last_name → lastname
- mobile → phone
- email → email (deduplication key)
- city, state, country mapped as-is
See the HubSpot CRM integration guide for the full sync architecture.
Non-Latin Input
All text fields are passed through sanitize_text_field() or sanitize_textarea_field(), which are UTF-8 safe. Names, addresses, and notes preserve any script correctly.