WPResidence CRM Enquiries — Developer Reference
This article documents how the Enquiries module is implemented in the WPResidence real estate CRM plugin. It covers the database table, the CRUD API, the AJAX endpoints, the auto-contact linking logic, status picklists, and the hooks you can use to extend the module.
All references are against plugin version 5.2.2 (wp-content/plugins/wpestate-crm/). Text domain: wpestate-crm.
Storage — wp_wpresidence_crm_enquiries
Enquiries are stored in a dedicated custom table — not as a CPT. The table is created in libs/db-setup.php around lines 128-161 via dbDelta.
Schema
| Column | Purpose |
|---|---|
enquiry_id |
Primary key, auto-increment |
user_id |
Owner (the agent the enquiry belongs to) |
lead_id |
FK to wp_wpresidence_crm_leads (nullable) |
contact_id |
FK to wp_wpresidence_crm_contacts |
enquiry_type |
Picklist crm_enquiry_types |
enquiry_status |
Picklist crm_enquiry_statuses |
enquiry_source |
Phone / Walk-in / Email / Social Media / Referral / Other |
enquiry_user_type |
Buyer / Seller / Tenant / Landlord / Investor / Other |
enquiry_to |
Assigned recipient |
enquiry_message |
Free-text message body |
property_id |
FK to estate_property post ID (nullable) |
property_type, property_status, property_city, property_area, property_country, property_state |
Denormalized property metadata |
min_price, max_price, min_bedrooms, max_bedrooms, min_bathrooms, max_bathrooms, min_area_size, max_area_size |
Search criteria from the prospect |
enquiry_meta |
longtext for JSON-encoded extra data |
created_at, updated_at |
Timestamps with CURRENT_TIMESTAMP |
Indexes
idx_lead_idonlead_ididx_user_idonuser_ididx_statusonenquiry_status
CRUD API
All functions live in libs/enquiry-crud.php. They accept and return plain arrays / objects; no CPT APIs are used.
| Function | Signature | Purpose |
|---|---|---|
wpestate_crm_insert_enquiry |
($data) : int|WP_Error |
Insert a new row, return enquiry_id |
wpestate_crm_get_enquiry |
($enquiry_id) : object|null |
Fetch one row by ID |
wpestate_crm_get_enquiries |
($args) : array |
Fetch multiple rows with filters |
wpestate_crm_update_enquiry |
($enquiry_id, $data) : true|WP_Error |
Update a row |
wpestate_crm_delete_enquiry |
($enquiry_id) : true|WP_Error |
Delete a row |
wpestate_crm_bulk_delete_enquiries |
($ids) : int |
Delete many, return affected count |
wpestate_crm_count_enquiries |
($args) : int |
Count rows matching filters |
wpestate_crm_sanitize_enquiry_data |
($data) : array |
Whitelist + sanitize an input array |
Accepted Arguments for get_enquiries()
user_id— ownership filterstatus—enquiry_statusvalueenquiry_typecontact_id,lead_iddate_from,date_to— range oncreated_atsearch— fuzzy match onenquiry_message,property_city,property_typeorderby— defaultcreated_at; allowed:enquiry_id,enquiry_status,enquiry_type,created_at,updated_atorder—ASCorDESClimit— default 20offset— for pagination
Sanitization
Every write path goes through wpestate_crm_sanitize_enquiry_data(). It applies sanitize_text_field(), sanitize_email(), and typed casts to numeric fields. Non-Latin input is preserved because sanitize_text_field() is UTF-8 safe.
Page Template — wpestate-crm-dashboard_enquiries.php
File: page-templates/wpestate-crm-dashboard_enquiries.php. The template has two modes selected by the enquiry_edit query parameter:
?enquiry_edit=new— add form (edit_id = 0).?enquiry_edit={id}— edit form, pre-filled fromwpestate_crm_get_enquiry().- No parameter — list view with pagination (
crm_pagequery var, 20 per page).
The form partial is templates/crm_add_enquiry.php. Deletion is handled inline via a GET parameter delete_enquiry_id with a capability check, after which the template redirects back to the enquiries URL.
Scoping the List
Non-administrators pass their own user ID into get_enquiries() via user_id. Administrators omit the argument to see all rows. Scope is resolved with wpestate_return_agent_list() at the top of the template.
AJAX Endpoint — Save Enquiry
One AJAX action handles both create and update.
| Property | Value |
|---|---|
| Action name | wpestate_crm_save_enquiry |
| Handler | wpestate_crm_ajax_save_enquiry() — lines 357-428 of enquiry-crud.php |
| Nonce action | wpestate_crm_save_enquiry |
| Nonce source in JS | wpestate_crm_vars.nonces.save_enquiry |
| Capability | wpestate_crm_user_can('contacts', 'create') |
| Form element | #crm-enquiry-form |
| JS submit binding | Lines 1588-1600 in js/crm-dashboard.js |
Handler Flow
- Verify nonce (
check_ajax_referer). - Check capability; return JSON error on failure.
- Collect
$_POSTfields:enquiry_source,enquiry_type,enquiry_status,enquiry_user_type,enquiry_message,property_id,property_city,property_type,contact_name,contact_email,contact_phone. - Call
wpestate_crm_find_contact_by_email($email)(line 388). - If the contact exists, attach its
contact_idto the data payload. If not, callwpestate_crm_insert_contact()with the split name, email, phone,lead_source= enquiry_source,lifecycle_stage='new_lead', and attach the new ID. - If
enquiry_id> 0, callwpestate_crm_update_enquiry(). Otherwise callwpestate_crm_insert_enquiry(). - Return
wp_send_json_success(['enquiry_id' => ...])orwp_send_json_error().
Auto-Contact Linking
This is the key design decision for Enquiries: there is no explicit “convert” action. Every enquiry save runs the email-match pipeline, so by the time the row is written the contact_id column is always populated.
Status Picklist
Two settings in libs/settings.php drive the picklists:
crm_enquiry_statuses— default:"New, Open, Responded, Closed"(lines 41, 161).crm_enquiry_types— default:"Property Enquiry, General Enquiry, Valuation Request, Viewing Request".
Both are stored as comma-separated strings in the CRM settings and are editable from the CRM Settings admin page. Add or rename values there — the form and filters pick up the changes automatically.
Form Template — crm_add_enquiry.php
File: templates/crm_add_enquiry.php. The template renders four sections (Enquiry Details, Contact Info, Property Details, Message) and includes two hidden fields:
enquiry_id—0for new, positive integer for edit (line 92).property_id— populated by the property title autocomplete (line 96).- Nonce field uses action
wpestate_crm_save_enquiry(line 100).
Permissions and Nonces
| Operation | Capability key | Nonce action |
|---|---|---|
| Save enquiry | wpestate_crm_user_can('contacts', 'create') |
wpestate_crm_save_enquiry |
| Delete enquiry | wpestate_crm_user_can('enquiries', 'delete') |
WP nonce on the delete URL |
The permissions matrix is defined per role in libs/permissions.php.
Notifications
Email notifications are rendered by wpestate_crm_resolve_placeholders() (libs/notifications.php lines 32-107), which supports 20 placeholders including {contact_name}, {contact_email}, {listing_title}, {agent_name}, {site_name}, and {crm_contact_url}.
Emails are wrapped by wpestate_crm_wrap_email_template() (lines 124-150), which uses templates/email_templates/base_email_template.php and respects the theme option wpestate_email_type (html or text).
Scheduled reminders fire from the cron events registered during activation — in particular wpestate_crm_daily_notifications and wpestate_crm_process_automations.
Integrations and Extensibility
- HubSpot. Enquiry-created and contact-created events can push to HubSpot. See HubSpot CRM integration for WPResidence.
- Webhooks. Outbound webhooks are dispatched from
libs/webhook-functions.php. - Automations. 27 default rules are seeded at activation; rules with enquiry triggers run in the
wpestate_crm_process_automationscron. - Extending fields. Store extra data in the
enquiry_metalongtextcolumn as JSON — no schema change required. Render it via the custom field renderer inlibs/field-renderer.php.
Where to Read the Code
libs/db-setup.php— table creation (lines 128-161).libs/enquiry-crud.php— CRUD and the save AJAX handler.libs/settings.php— picklists for types and statuses.libs/notifications.php— email placeholders and template wrapper.page-templates/wpestate-crm-dashboard_enquiries.php— list + edit routing.templates/crm_add_enquiry.php— the form partial.js/crm-dashboard.js— form submission at lines 1588-1600.