For developers

What developers can build on: hooks, shortcodes, blocks, REST routes and WP-CLI commands, taken from each plugin’s own readme and from what this site has registered.

DoctorX Listing Data Model

Use these functions rather than the custom fields directly. They are available once plugins have loaded; save listings on or after the init action. Check for the plugin with defined( 'DOCTORX_LISTINGS_API' ) (version 1 of the API) or function_exists( 'doctorx_listings_upsert' ).

doctorx_listings_upsert( $source, $source_id, array $data, array $args = array() )

Creates the listing a source knows by $source_id, or updates it. Safe to call for every record of every sync: when nothing changed, nothing is written.

  • $source: your source name, lowercase letters, digits, dashes and underscores, up to 40 characters (for example 'crea-ddf'). 'manual' is used for listings entered by hand.
  • $source_id: the source's own ID for the listing, up to 150 characters, compared exactly (for RESO feeds, use ListingKey).
  • $data: field => value, using the names above (any letter case) or their meta keys. Also: 'title' (post title; default the address, or "Semi-Detached in Glebe, Ottawa" when the street address may not be shown), 'PublicRemarks' (the description, plain text) and 'Media' (accepted for 'photos'). null or '' clears a field.
  • Photos accept: a list of web addresses; a list of attachment IDs; or a list of arrays with url or MediaURL, id, caption or ShortDescription, and order or Order. Entries whose MediaCategory is not a photo (a tour, a document) are left out. At most 100 photos (filter doctorx_listings_max_photos).
  • When 'StandardStatus' is given and 'status' is not, the status is worked out from it (Active → active, Active Under Contract or Pending → pending, Closed or Sold → sold, or leased for a lease, Expired, Withdrawn, Canceled and the like → off_market). An unknown status keeps the listing off the site and is reported in 'skipped'.
  • $args: 'post_status' (publish, draft, pending or private; default publish for a new listing and unchanged for an existing one; a listing in the bin is restored), 'replace' (true clears every field not in $data; default false, a partial update), 'author' (user ID for a new listing), 'keep_unknown' (true keeps fields this plugin does not know in 'extra'), 'force' (write even when nothing changed).
  • Returns array( 'post_id' => int, 'created' => bool, 'changed' => bool, 'skipped' => array( field => reason ) ), or WP_Error (bad source or ID, called too early, busy, or WordPress could not save).
  • A listing the feed says may not be shown on the internet at all (RESO InternetEntireListingDisplayYN false) should not be published: skip it, remove it, or upsert it with 'post_status' => 'draft'. InternetAddressDisplayYN false is handled here (the street address is hidden).
$r = doctorx_listings_upsert( 'crea-ddf', $row['ListingKey'], array(
    'ListingId'       => $row['ListingId'],
    'StandardStatus'  => $row['StandardStatus'],
    'ListPrice'       => $row['ListPrice'],
    'UnparsedAddress' => $row['UnparsedAddress'],
    'City'            => $row['City'],
    'CityRegion'      => $row['CityRegion'],
    'StateOrProvince' => $row['StateOrProvince'],
    'InternetAddressDisplayYN' => $row['InternetAddressDisplayYN'],
    'ListOfficeName'  => $office_name,
    'PublicRemarks'   => $row['PublicRemarks'],
    'Media'           => $row['Media'],
) );

doctorx_listings_remove( $source, $source_id, $mode = '' )

Takes a source's listing off the site. $mode: 'trash' (the default: it leaves the site at once and WordPress empties the bin after 30 days; an upsert of the same ID restores it), 'delete' (gone at once, with the media uploaded to it) or 'off_market' (kept and marked off the market). Returns true when removed, false when the site had no such listing, or WP_Error for an unknown mode.

doctorx_listings_source_ids( $source )

Every listing one source has on the site (not counting the bin), as source ID => post ID. Compare it with your feed to find the listings it no longer sends, then remove them.

doctorx_listings_find( $source, $source_id )

The post ID of a source's listing in any status, the bin included, or 0.

doctorx_listings_get( $post_id, $context = 'view' )

A listing's values in their types (numbers as int or float, yes/no as bool, empty as null), plus post_id, title, url, PublicRemarks, post_status, address_line (the street address on one line, '' when hidden), days_on_market, photos as array( url, id, caption, alt, remote ), Rooms as array( level, type, dimensions, description ), and owner_media (kind => value, see below). 'view' gives what visitors may see: private fields and source keys are left out, the street fields are null when the address is hidden, and ClosePrice and CloseDate are null unless sold prices are allowed. 'edit' gives everything, with source, source_id and synced_at. Returns null when the post is not a listing.

doctorx_listings_set_owner_media( $post_id, $kind, $value )

Keeps media the site owner added to a listing, such as a tour video, next to the listing's data. Feed updates never touch it, not even doctorx_listings_upsert() with 'replace' => true; it goes when the listing is deleted. doctorx_listings_get() returns it under 'owner_media', as kind => value, in both contexts, so store only what may be public.

  • $kind: a short name of lowercase letters, digits, dashes and underscores (DoctorX Listing Video Uploader uses 'tour_video').
  • $value: text, a number, yes/no, or an array of those up to three levels deep and under 20 KB; text is stored as plain text. null removes that kind.
  • Returns true, or WP_Error when the post is not a listing, the kind is not usable, or the value is not storable.
if ( function_exists( 'doctorx_listings_set_owner_media' ) ) {
    doctorx_listings_set_owner_media( $post_id, 'tour_video', array( 'provider' => 'youtube', 'id' => $video_id ) );
}

doctorx_listings_get_owner_media( $post_id, $kind = '' ) returns one kind's value (or null), or every kind when $kind is ''.

doctorx_listings_walk( array $fields, callable $callback, array $args = array() )

Visits every published listing without loading whole posts, a chunk at a time: $callback( $post_id, $values ) receives the fields asked for, plus status and transaction. By default only listings visitors may see, with the visitor rules applied ('public_only' => false for all of them, unfiltered; 'chunk' => 500). Returns the number visited.

Other functions

  • doctorx_listings_post_type(): 'drx_listing'.
  • doctorx_listings_fields(): every field with its meta key, type, label, group and flags.
  • doctorx_listings_meta_key( $field ): the meta key of a field ('ListPrice' → '_drx_list_price').
  • doctorx_listings_is_public( $post_id ): whether visitors may see the listing.
  • doctorx_listings_visible_statuses(): the statuses visitors see.
  • doctorx_listings_normalize_status( $standard_status, $transaction = 'sale' ): a source status word as a listing status, or ''.
  • doctorx_listings_sold_prices_allowed(): whether sold prices may be shown publicly.
  • doctorx_listings_currency(): the three-letter currency of prices (Listings → Settings, default CAD).
  • doctorx_listings_format_price( $amount, $frequency = '' ): "$1,250,000", "$2,400/month".
  • doctorx_listings_photo_classes( $post_id ): CSS classes for a listing's photos: doctorx-listing-source-<source>, plus drx-listing-uncropped when the photos must be shown whole (use object-fit: contain for it). The same classes are on the listing page's body and on the listing in post lists, and the featured image of such a listing gets object-fit: contain.

Actions

  • doctorx_listings_saved( $post_id, $created, $source, $source_id ): after doctorx_listings_upsert() wrote a listing (not when nothing changed).
  • doctorx_listings_removed( $post_id, $source, $source_id, $mode ): after doctorx_listings_remove().
  • doctorx_listings_after_facts( $post_id, $values ): print more under a listing page's description (DoctorX Neighbourhood Guides adds its link here).
  • doctorx_listings_owner_media_set( $post_id, $kind, $value ): after doctorx_listings_set_owner_media() ($value null when removed).

Filters

  • doctorx_listings_fields( $fields ): add, relabel or hide fields. A new field needs a unique 'meta' key; removing a built-in field breaks plugins that use it.
  • doctorx_listings_upsert_data( $data, $source, $source_id, $args ): change incoming data before it is saved.
  • doctorx_listings_status_map( $map ): add source status words (lowercase letters only, e.g. 'activeundercontract') => listing status.
  • doctorx_listings_visible_statuses( $statuses ): the statuses visitors see.
  • doctorx_listings_sold_prices_allowed( $allowed ): force false when a feed licence forbids sold prices.
  • doctorx_listings_remove_mode( $mode, $source, $source_id, $post_id ): what doctorx_listings_remove() does when no mode is given.
  • doctorx_listings_delete_attachments( $delete, $post_id ): whether deleting a listing deletes the media uploaded to it (default true).
  • doctorx_listings_max_photos( $max ): most photos kept per listing (default 100).
  • doctorx_listings_data( $values, $post_id, $context ): the values doctorx_listings_get() returns.
  • doctorx_listings_rest_fields( $names ): the fields the REST API shows.
  • doctorx_listings_facts_rows( $rows, $values, $post_id ): the rows of the facts table on a listing page.
  • doctorx_listings_field_label( $label, $field, $source, $post_id ): a label in the facts and rooms tables, per source (for example, call the listing number "Ref." for your own listings).
  • doctorx_listings_mls_sources( $sources ): sources whose listing number is labelled "MLS® number". Default array( 'crea-ddf' ), the source of DoctorX Listing Sync for CREA DDF®. Add yours only if its listings come from an MLS® System.
  • doctorx_listings_uncropped_photos( $uncropped, $source, $post_id ): whether a listing's photos are shown whole. Default true for every source except listings entered by hand.
  • doctorx_listings_post_type_args( $args ): register_post_type() arguments.
  • doctorx_listings_use_block_editor( $use ): edit listings in the block editor (default false, the classic screen with the field boxes).

REST API

Listings are at /wp-json/wp/v2/listings with a read-only "listing" object holding the REST-safe fields by RESO name, address_line and days_on_market. Visitors get the same view as doctorx_listings_get( $id, 'view' ); hidden listings answer "not found". People who can edit a listing get every field with ?context=edit. The custom fields themselves are not exposed. To write listings, use doctorx_listings_upsert() in PHP.

WP-CLI

  • wp doctorx-listings fields: the field list with meta keys.
  • wp doctorx-listings get <id> [–all]: a listing as JSON.
  • wp doctorx-listings upsert <source> <source_id> <file.json|-> [–replace]
  • wp doctorx-listings remove <source> <source_id> [–mode=trash|delete|off_market]

DoctorX Listing Schema

Filters

  • doctorx_listing_schema_print( $print, $what, $post_id ): whether to print the 'listing' (RealEstateListing) or the 'breadcrumbs' (BreadcrumbList) on this listing page. Return false when your theme or plugin prints its own. This is the filter to coordinate duplicates with.
  • doctorx_listing_schema_data( $data, $post_id, $values ): the RealEstateListing data before it is printed; return an empty array to print none for this listing.
  • doctorx_listing_schema_breadcrumbs( $trail, $post_id ): the trail as array( name, url ) steps, home first, the listing last. DoctorX Neighbourhood Guides inserts the neighbourhood here.
  • doctorx_listing_schema_place_type( $type, $values ): the schema.org type of the property (Accommodation, House, SingleFamilyResidence, Apartment, ApartmentComplex, Residence or Place).
  • doctorx_listing_schema_post_types( $types ): the post types whose pages get listing data.

Functions

  • doctorx_listing_schema_for_post( $post_id ): the RealEstateListing data a page carries, as an array.
  • doctorx_listing_schema_claim(): this page's listing data is printed by someone else; print none.

When it prints a trail, the plugin calls doctorx_seo_breadcrumb_claim() (DoctorX SEO), and it does not print one when doctorx_seo_breadcrumb_claimed() says a trail is already taken. It also tells DoctorX SEO's structured-data check that Google documents no requirements for RealEstateListing.

WP-CLI

  • wp doctorx-listing-schema render <id>: the structured data of a listing page, as JSON.

DoctorX Neighbourhood Guides

Guides are posts of type drx_neighbourhood. The figures are in the custom field _drx_guide_stats; the area in _drx_guide_key (a stable lowercase id, "city–neighbourhood"), _drx_guide_city and _drx_guide_area.

Shortcode

places the figures (and the homes listed, unless listings="no") inside a guide's text instead of after it.

Filters and actions

  • doctorx_guides_area_for_listing( $area, $values, $post_id, $grouping ): the area a listing counts in, as array( key, city, area ), or null to leave it out.
  • doctorx_guides_snapshot( $figures, $area ): the figures stored for a guide; add your own.
  • doctorx_guides_figures_rows( $rows, $figures, $guide_id ): the rows shown on a guide (label => text).
  • doctorx_guides_ai_instructions( $instructions ): the instructions given to the AI for an introduction.
  • doctorx_guides_post_type_args( $args ): register_post_type() arguments.
  • doctorx_guides_draft_created( $guide_id, $area ) (action): a draft guide was created.

WP-CLI

  • wp doctorx-neighbourhood-guides areas: the areas found in the listings.
  • wp doctorx-neighbourhood-guides build [–create] [–ai=<n>]: update the figures; create drafts for new areas.
  • wp doctorx-neighbourhood-guides show <id>: a guide's figures as JSON.

DoctorX Listing Video Uploader

  • Shortcodes:
  • Blocks: doctorx-listing-video/tour

No developer hooks beyond the shortcodes and blocks above.

DoctorX Agent Profile theme

No developer hooks beyond the shortcodes and blocks above.

DoctorX demo. A made-up business: please use made-up details. Try it as Customer, Owner or Developer.Demo · Try itGet DoctorX Agent Profile and Realtor KitGet it