| 1 |
<?php |
| 2 |
/** |
| 3 |
* Standalone (theme_id 990) listing-card view model + hook surface. |
| 4 |
* |
| 5 |
* Every property grid renders through Mlsimport_Standalone_Render::render_cards(), |
| 6 |
* which picks the card template (v1/v2/v3) via mlsimport_standalone_card_template(). |
| 7 |
* Each card template builds its data once from this one helper, so the three |
| 8 |
* designs stay consistent and a new card design is a thin template, not a new data |
| 9 |
* pass. The card action slots below are the WooCommerce-style extension points |
| 10 |
* fired inside every card template. |
| 11 |
* |
| 12 |
* Card action slots (each fired with ( WP_Post $post, object|null $row )): |
| 13 |
* action mlsimport_card_before — just after <article> opens (ribbons/badges) |
| 14 |
* action mlsimport_card_after_media — direct child of <article>, after the media/link |
| 15 |
* (favorite heart, gallery count); positioned over |
| 16 |
* the photo via CSS, so it sits OUTSIDE the card's |
| 17 |
* <a> link and fires on thumbnail-less cards too |
| 18 |
* action mlsimport_card_body_start — top of the card body |
| 19 |
* action mlsimport_card_body_end — bottom of the card body (CTA buttons) |
| 20 |
* action mlsimport_card_after — just before </article> closes |
| 21 |
* Filter: |
| 22 |
* filter mlsimport_card_view ( array $view, WP_Post $post, object|null $row ) |
| 23 |
* |
| 24 |
* @package Mlsimport |
| 25 |
*/ |
| 26 |
|
| 27 |
if ( ! defined( 'ABSPATH' ) ) { |
| 28 |
exit; |
| 29 |
} |
| 30 |
|
| 31 |
/** |
| 32 |
* Build one listing card's view model from the post + its listings row. Pulls the |
| 33 |
* clean RESO address, formatted price, spec line, status and agency, so the card |
| 34 |
* templates only lay out — they don't re-derive. |
| 35 |
* |
| 36 |
* @param WP_Post $post The listing post. |
| 37 |
* @param object|null $row The mlsimport_listings row (searchable scalars). |
| 38 |
* @return array |
| 39 |
*/ |
| 40 |
function mlsimport_card_view( $post, $row ): array { |
| 41 |
// Cast the post ID once for the meta/permalink/thumbnail lookups below. |
| 42 |
$pid = (int) $post->ID; |
| 43 |
|
| 44 |
// Address: the clean RESO UnparsedAddress is the heading; the raw post title |
| 45 |
// (often an importer concatenation) is only the fallback. |
| 46 |
$address = trim( (string) get_post_meta( $pid, 'mlsimport_UnparsedAddress', true ) ); |
| 47 |
if ( '' === $address ) { |
| 48 |
$address = get_the_title( $pid ); |
| 49 |
} |
| 50 |
|
| 51 |
// Price: keep null when the row has none; otherwise format as "$ 1,234,567". |
| 52 |
$price = ( isset( $row->price ) && null !== $row->price && '' !== $row->price ) ? (float) $row->price : null; |
| 53 |
$price_fmt = null !== $price ? '$ ' . number_format( $price ) : ''; |
| 54 |
|
| 55 |
// Spec line: only the parts present. |
| 56 |
$specs = array(); |
| 57 |
// Beds — pluralized; drop the decimal for whole numbers. |
| 58 |
if ( isset( $row->bedrooms ) && null !== $row->bedrooms && '' !== $row->bedrooms ) { |
| 59 |
$b = (float) $row->bedrooms; |
| 60 |
$specs[] = sprintf( _n( '%s bed', '%s beds', (int) ceil( $b ), 'mlsimport' ), number_format_i18n( $b, ( $b === (float) (int) $b ) ? 0 : 1 ) ); |
| 61 |
} |
| 62 |
// Baths — pluralized; drop the decimal for whole numbers. |
| 63 |
if ( isset( $row->bathrooms ) && null !== $row->bathrooms && '' !== $row->bathrooms ) { |
| 64 |
$ba = (float) $row->bathrooms; |
| 65 |
$specs[] = sprintf( _n( '%s bath', '%s baths', (int) ceil( $ba ), 'mlsimport' ), number_format_i18n( $ba, ( $ba === (float) (int) $ba ) ? 0 : 1 ) ); |
| 66 |
} |
| 67 |
// Living area — formatted number plus the ft² unit. |
| 68 |
if ( isset( $row->living_area ) && null !== $row->living_area && '' !== $row->living_area ) { |
| 69 |
$specs[] = number_format_i18n( (float) $row->living_area ) . ' ' . __( 'ft²', 'mlsimport' ); |
| 70 |
} |
| 71 |
|
| 72 |
// Assemble the view model the card templates lay out (no further deriving). |
| 73 |
$view = array( |
| 74 |
'id' => $pid, |
| 75 |
'permalink' => (string) get_permalink( $pid ), |
| 76 |
'address' => $address, |
| 77 |
'price' => $price, |
| 78 |
'price_fmt' => $price_fmt, |
| 79 |
'status' => isset( $row->status ) ? trim( (string) $row->status ) : '', |
| 80 |
'specs' => $specs, |
| 81 |
'office' => trim( (string) get_post_meta( $pid, 'mlsimport_ListOfficeName', true ) ), |
| 82 |
'logo' => function_exists( 'mlsimport_standalone_mls_logo_url' ) ? mlsimport_standalone_mls_logo_url() : '', |
| 83 |
'has_thumb' => has_post_thumbnail( $pid ), |
| 84 |
// Photo URL for the card media — rendered as a CSS background-image (never an |
| 85 |
// <img>), the same convention as the map/featured cards. |
| 86 |
'thumb' => has_post_thumbnail( $pid ) ? (string) get_the_post_thumbnail_url( $pid, 'large' ) : '', |
| 87 |
); |
| 88 |
|
| 89 |
/** Filter one card's view model before the template lays it out. @since 6.4 */ |
| 90 |
return (array) apply_filters( 'mlsimport_card_view', $view, $post, $row ); |
| 91 |
} |
| 92 |
|