'' ); } // Raw RESO fields plus the accumulators used while routing them. $extra = $property['extra_meta']; $terms_by_tax = array(); $opted_in = $this->opted_in_fields(); $listing_key = isset( $extra['ListingKey'] ) ? (string) $extra['ListingKey'] : ''; $incoming_mod = isset( $extra['ModificationTimestamp'] ) ? (string) $extra['ModificationTimestamp'] : ''; /** Fires before a property is written (or skipped). @since 6.3 */ do_action( 'mlsimport_before_save_property', $property_id, $property ); // Sole change signal (ADR-0002): skip the whole write when the stored row // is at least as fresh as the incoming ModificationTimestamp. The verdict is // filterable so add-ons can force or suppress a write. $skip = '' !== $listing_key && '' !== $incoming_mod && $this->is_unchanged( $listing_key, $incoming_mod ); /** Filter whether to skip writing this property. @since 6.3 */ if ( apply_filters( 'mlsimport_property_skip_write', $skip, $property_id, $incoming_mod, $property ) ) { return array( 'property_history' => 'skipped: not modified since last import' ); } // Route every incoming RESO field to its configured target(s). foreach ( $extra as $field => $value ) { $is_array = is_array( $value ); // A field may map to multiple targets (meta, tax, feature...). foreach ( Mlsimport_Standalone_Reso_Map::targets_for( $field ) as $target ) { // 'skip'/'content' are handled elsewhere (or not at all) here. if ( 'skip' === $target || 'content' === $target ) { continue; } // Target format is ":" (e.g. "meta:x_foo", "tax:mlsimport_view"). $parts = explode( ':', $target, 2 ); $kind = $parts[0]; $name = isset( $parts[1] ) ? $parts[1] : ''; // Target kind 1: post meta. if ( 'meta' === $kind ) { // Meta key is namespaced; an "x_" name marks a passthrough field. $meta_key = 'mlsimport_' . $name; $is_passthrough = 0 === strpos( $name, 'x_' ); $raw = $value; if ( $is_array ) { // A multi-value RESO field (Flooring, Cooling, PoolFeatures...) // stores as a comma-joined string, mapped or passthrough alike — // that string is what the property page prints as the field's row. // Arrays of records (Rooms, Media) have no scalar members and so // store nothing. $raw = implode( ', ', array_filter( $value, 'is_scalar' ) ); if ( '' === $raw ) { continue; } } /** Filter a property meta value before write. @since 6.3 */ $meta_value = apply_filters( 'mlsimport_property_meta_value', $raw, $meta_key, $property_id, $property ); if ( $is_passthrough ) { // Passthrough: write only when the field is opted in via the selector. if ( isset( $opted_in[ $field ] ) && 1 === intval( $opted_in[ $field ] ) ) { update_post_meta( $property_id, $meta_key, $meta_value ); } continue; } update_post_meta( $property_id, $meta_key, $meta_value ); // Target kind 2: taxonomy terms. } elseif ( 'tax' === $kind ) { // Multi-enum arrays (Appliances, View...) -> one term per value. foreach ( ( $is_array ? $value : array( $value ) ) as $term ) { // Collect non-empty term names under their taxonomy. if ( '' !== (string) $term ) { $terms_by_tax[ $name ][] = (string) $term; } } // Target kind 3: boolean "feature" flag -> label term when true. } elseif ( 'feature' === $kind && ! $is_array && $this->is_truthy( $value ) ) { // feature: