The Havenlytics property engine is the set of functions and renderers that turn a hvnly_property post into a rendered card or single-property page. This guide follows the render pipeline end to end — from the query, through the loop, into the view model, and out to the template — and documents the helper functions you call when extending it.
Note: The property engine functions live in
includes/Functions/and the renderers inincludes/Frontend/. Every property field is stored as post meta on thehvnly_propertyCPT.
The render pipeline
Rendering a property archive follows four stages:
- Query —
PropertyQueryExecutor::query()returns aWP_Queryofpost_type=hvnly_property. - Loop — for each post, call
hvnly_render_property_card( get_the_ID() ). - View model — the renderer builds the card’s data through
hvnly_get_property_data(). - Template —
PropertyCardRenderer::render_property_card_dynamic()chooses the preset or default path and loads the matchingtemplates/archive/**partials.
Single-property pages are handled by includes/Frontend/PropertySingleRenderer.php, reachable through the accessor hvnly_get_property_single_renderer().
The property view model: hvnly_get_property_data()
hvnly_get_property_data( $property_id ) (in template-functions.php) is the canonical, statically-cached view model for a property. Call it whenever you need a normalized array of a property’s display data rather than reading meta keys one by one. The returned array is filtered through hvnly_property_data.
Returned keys
| Key | Meaning / source |
|---|---|
id | Property post ID. |
title | Property title. |
permalink | Property URL. |
excerpt | Property excerpt. |
price | From _hvnly_property_price. |
address | From _hvnly_property_address_line_1. |
phone | Contact phone. |
bedrooms | Bedroom count. |
bathrooms | Bathroom count. |
area | From _hvnly_property_sqft. |
garage | From _hvnly_property_garage_sqft. |
is_featured | Featured flag. |
author_id | Author (listing owner) ID. |
featured_image | Featured image. |
gallery_image_ids | Attachment IDs from the gallery SSOT loader. |
gallery_order | Gallery ordering. |
locations | Location terms. |
categories | Category/type terms. |
author_posts_url | Author archive URL. |
current_filters | Active search filters in context. |
view_type | grid / list / map context. |
Performance:
hvnly_get_property_data()is statically cached per property per request. Repeated calls for the same ID within one request are essentially free, so prefer it over re-reading individual meta keys in a loop.
Gallery SSOT: hvnly_get_property_gallery_ids()
hvnly_get_property_gallery_ids( $property_id ) (in template-functions.php) is the single source of truth for a property’s gallery. Every avatar-free image surface must route through it rather than reading meta directly.
Internally it locates the gallery field(s) from hvnly_property_builder.sections, parses the stored value with hvnly_parse_gallery_attachment_ids() (which accepts CSV, JSON, or a single ID), then normalizes the result with hvnly_normalize_gallery_attachment_ids() — deduplicating and keeping only IDs where wp_attachment_is_image() is true. The stored value itself lives under {group_base_id}_images as a CSV of attachment IDs.
Common mistake: Reading a raw
{base}_imagesmeta key and callingexplode( ',', ... )yourself. That bypasses image validation and deduplication. Always callhvnly_get_property_gallery_ids().
Core rendering & field helpers
| Function | Role |
|---|---|
hvnly_render_property_card( $id ) | Renders a full property card (delegates to PropertyCardRenderer). |
hvnly_render_field() | Renders a single configured field via its template slug. |
hvnly_is_field_configured() | Checks whether a field type is present in the card configuration. |
hvnly_has_avatar_section() | Checks whether the card layout includes the avatar section. |
hvnly_get_configured_field_types() | Lists the field types currently configured on the card. |
hvnly_resolve_field_meta( int $post_id, array $field, string $primary = '' ) | Group-aware meta resolver (in utility-functions.php) that honours group_base_id + metaKey. |
hvnly_resolve_field_meta() is the correct way to read a builder field’s value, because it understands the group-identity metadata rather than assuming a fixed meta key — the same mechanism that keeps values intact across field renames.
Price helpers
hvnly_get_currency_settings()/hvnly_get_currency_symbol()— resolve the configured currency.hvnly_format_price()— format a numeric price for display.hvnly_resolve_property_price()— resolve a property’s effective price value.hvnly_safe_price_to_float()— coerce a stored price string to a float safely.hvnly_format_large_number_with_suffix()— abbreviate large numbers (e.g. K / M suffixes).
Taxonomies
Properties are organised by these taxonomies: hvnly_prop_depts, hvnly_prop_types, hvnly_prop_locations, hvnly_prop_features, hvnly_prop_reviews, hvnly_prop_tags, hvnly_prop_badges, and hvnly_prop_status, plus amenities.
Shortcodes & view models
Front-end entry points live in includes/Frontend/Shortcodes/ — PropertyGrid, PropertyList, PropertySearch, FeaturedProperties, a Registry, and an AbstractShortcode base. Their presentation logic is backed by view models in Frontend/ViewModels/, including SearchFilters and SidebarSearchFilters.
Code examples
Render property cards in a custom loop
<?php
/**
* Render a custom grid of properties using the same renderer as the
* core archive, so cards match the saved Card Builder layout.
*/
$q = new WP_Query( array(
'post_type' => 'hvnly_property',
'post_status' => 'publish',
'posts_per_page' => 6,
) );
if ( $q->have_posts() ) {
echo '<div class="my-property-grid">';
while ( $q->have_posts() ) {
$q->the_post();
echo hvnly_render_property_card( get_the_ID() );
}
echo '</div>';
wp_reset_postdata();
}
Read a property’s gallery through the SSOT
<?php
$ids = hvnly_get_property_gallery_ids( get_the_ID() );
foreach ( $ids as $attachment_id ) {
echo wp_get_attachment_image( $attachment_id, 'medium' );
}
Filter the property view model
<?php
/**
* Add a derived "price_per_sqft" value to every property view model.
* Runs whenever hvnly_get_property_data() builds a property's data.
*/
add_filter( 'hvnly_property_data', function ( array $data ) {
$price = hvnly_safe_price_to_float( $data['price'] );
$area = (float) $data['area'];
$data['price_per_sqft'] = ( $area > 0 )
? hvnly_format_price( $price / $area )
: '';
return $data;
} );
Best practices
- Always render cards with
hvnly_render_property_card()so custom loops honour the Card Builder layout automatically. - Read display data from
hvnly_get_property_data(), not from scattered meta reads. - Resolve builder-field values with
hvnly_resolve_field_meta()to stay compatible with group renames. - Use the price helpers for anything money-related so currency settings and formatting stay consistent site-wide.
Performance notes
- The view model and card renderer are request-scoped caches; keep heavy work inside the
hvnly_property_datafilter idempotent and cheap since it runs per property. - The gallery SSOT validates every attachment with
wp_attachment_is_image(); avoid calling it in tight inner loops for the same property — cache the returned array.
Security notes
hvnly_render_property_card()returns markup that already escapes field output through its templates; if you add fields to thehvnly_property_datafilter, escape them in your own templates.- Never trust raw gallery meta — routing through
hvnly_get_property_gallery_ids()ensures only real image attachments are emitted.
Common mistakes
- Bypassing
hvnly_get_property_gallery_ids()and parsing the{base}_imagesCSV manually. - Reading builder fields by a guessed meta key instead of
hvnly_resolve_field_meta(). - Formatting prices with raw PHP
number_format()instead ofhvnly_format_price(), breaking currency configuration.