=== Core Listing ===
Contributors: corelisting
Tags: real estate, commercial real estate, listings, cre, crm
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.5.7
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Embed live commercial real estate listings, sold deals and your team roster from your Core Listing account using a shortcode or block.

== Description ==

Core Listing is a commercial real estate listing and CRM platform for brokerages. This plugin connects your WordPress site to your Core Listing account so the inventory you already maintain there appears on your own website, and stays current without anyone re-keying it.

Add a shortcode or a block to any page and the plugin renders a responsive grid of listing cards built from live data. Each card links through to the full listing page on your Core Listing portal.

You decide what those cards say. In Core Listing, under Account Settings and then Listing Display, you choose the heading, the line beneath it and the detail rows: price, custom price text, square footage, units, beds, cap rate, occupancy, year built, class, parking, any account custom field, and more. Every label is renamable. You also choose where those details sit: below the photo, or beside it for a card that carries more rows. That arrangement is one setting for your whole brokerage, so every grid you publish is built the same way. The fields are not: because a hotel and a retail strip rarely want the same three numbers, you can set a different field list for each property type, and override a single listing outright when it needs its own. The plugin renders whatever you chose, so your website, your Core Listing portal and your sold page always agree.

= What you can embed =

* **Active inventory** with `[corelisting_listings]`. Filter by office, property type, status and price range; control sort order, result limit and column count.
* **Sold and closed transactions** with `[corelisting_sold]`, for a "Recent Closings" or track record page. Accepts the same filters, with status locked to sold.
* **A search page with a map** with `[corelisting_search]`: keyword, status, sale or lease, property type, price, size, year built and featured filters, sorting, and a map beside the list. Every listing loads with the page, so results update as the visitor types, and the list follows the map as they zoom and pan. Each control can be switched on, off, or off on mobile only, and the layout, pin colour and map style are all attributes. The map needs a Google Maps API key, entered on the settings screen.
* **Your public team roster** with `[corelisting_members]`, showing the photo, name, titles, licenses, biography, email and phone for every member flagged "Show on Team Page" in Core Listing. Scope it to a single office, or hide biographies with `show_bio="no"`.

The listings, sold and team shortcodes are also registered as blocks, so you can insert and configure them visually in the editor with a live preview and no shortcode syntax to remember. The search is shortcode only for now; place it with a Shortcode block.

= Built to stay in sync =

Listings are cached locally as WordPress transients, so a typical page view never makes an outbound request. When you change a listing in Core Listing, a signed webhook clears that cache within seconds, so your site is fast and current at the same time. Incoming webhooks are verified with an HMAC-SHA256 signature and rejected if the signature does not match.

If your brokerage has a verified white label domain on file, listing links automatically use it, so visitors stay on your own branded host.

= Styling =

Output is plain, semantic HTML with stable CSS class names such as `.corelisting-grid`, `.corelisting-card`, `.corelisting-photo` and `.corelisting-detail-value`. Every detail row also carries a class naming its field, for example `.corelisting-detail-price`, so a single row can be styled on its own. Restyle everything from Appearance to Customize to Additional CSS without touching plugin files.

= External services =

This plugin is a client for Core Listing, a third party software as a service platform at [corelisting.com](https://corelisting.com). It is only useful with an active Core Listing account, and it makes no outbound request at all until you save an API key on the settings screen.

Once connected, the plugin contacts `https://corelisting.com` (or your account's own white label domain, if you have configured one) in the following situations:

* **Rendering a shortcode or block, when the local cache is cold.** The plugin requests your listings, sold listings or team roster. It sends your API key for authentication and nothing else. No visitor information is included.
* **Clicking "Test Connection".** The plugin requests your account name and white label domain to confirm the API key works.
* **Once a day in the background.** The plugin re-checks your account's white label domain so listing links stay correct.
* **Clicking "Register Webhook with Core Listing".** The plugin sends this site's address, the plugin version and the REST webhook URL on this site, so Core Listing knows where to deliver listing change events. This is the only action that transmits your site address, and it only happens when you click the button.
* **Deleting the plugin.** The plugin tells Core Listing to release the webhook registration for this site, so events are no longer dispatched to a site that removed the integration. This is skipped entirely if no API key was ever saved.

The plugin itself never transmits visitor, customer or personally identifiable data from your WordPress site, sets no cookies and performs no tracking.

Pages using `[corelisting_search]` also load two third party services directly in the visitor's browser:

* **Google Maps**, when a Google Maps API key is saved on the settings screen. The browser loads the Maps JavaScript API from `https://maps.googleapis.com` to draw the map and pins. Google receives the visitor's IP address and browser details, as with any embedded Google map, and may set its own cookies. Without a key the map is not loaded at all and the search runs as a list. Google Maps terms: [cloud.google.com/maps-platform/terms](https://cloud.google.com/maps-platform/terms). Google privacy policy: [policies.google.com/privacy](https://policies.google.com/privacy).
* **OpenStreetMap Nominatim**, only when a listing has an address but no map coordinates and the shortcode's `geocode_missing` attribute is on (the default). The browser sends that listing's address to `https://nominatim.openstreetmap.org` to place its pin, and caches the answer in the browser so it is asked once. OpenStreetMap receives the visitor's IP address. Set `geocode_missing="no"` to turn this off. Usage policy: [operations.osmfoundation.org/policies/nominatim](https://operations.osmfoundation.org/policies/nominatim/). Privacy policy: [osmfoundation.org/wiki/Privacy_Policy](https://osmfoundation.org/wiki/Privacy_Policy).

Service terms: [corelisting.com/terms](https://corelisting.com/terms). Privacy policy: [corelisting.com/privacy-policy](https://corelisting.com/privacy-policy).

== Installation ==

1. Install the plugin through Plugins to Add New in your WordPress admin, or upload the plugin zip through Plugins to Add New to Upload Plugin.
2. Activate **Core Listing**.
3. In Core Listing, go to Integrations to WordPress and click **Generate WordPress API Key**, then copy the key.
4. In WordPress, go to **Settings to Core Listing**. Enter your Core Listing URL and paste the API key, then save.
5. Click **Test Connection** to confirm the key works, then **Register Webhook with Core Listing** so listing changes sync automatically.
6. Add `[corelisting_listings]` to any page, or insert the "Core Listing Listings" block.

For the Core Listing URL, use your confirmed white label domain (for example `https://listings.yourbrokerage.com`) so images are served from your own host. If you do not have one yet, use `https://corelisting.com`. Your subdomain on its own, such as `acme`, also works as shorthand for `https://acme.corelisting.com`.

== Frequently Asked Questions ==

= Do I need a Core Listing account? =

Yes. This plugin is a display client for the Core Listing platform and has no listing data of its own. Without an account and an API key it has nothing to show.

= Where do I get the API key? =

In Core Listing, go to Integrations to WordPress and click "Generate WordPress API Key". The key is scoped so it can only read the data this plugin needs. You can revoke it from the same screen at any time.

= What shortcodes are available? =

`[corelisting_listings]` for active inventory, `[corelisting_sold]` for sold and closed transactions, `[corelisting_search]` for a search page with a map, and `[corelisting_members]` for your public team roster. Every attribute is documented on the Settings to Core Listing screen.

= Where does the Google Maps API key come from? =

Core Listing creates it for you during onboarding and restricts it to your site's domain. Paste it into Settings to Core Listing. Until a key is saved, `[corelisting_search]` still works as a list without the map, and administrators see a short note saying why.

= I am upgrading from 1.3.1 and my listings disappeared. What happened? =

The shortcodes were renamed in 1.4.0, from `[crm_listings]`, `[crm_sold]` and `[crm_members]` to `[corelisting_listings]`, `[corelisting_sold]` and `[corelisting_members]`. Edit any page still using an old name and change it. Pages built with the blocks rather than the shortcodes are unaffected.

= Can I show listings from just one office? =

Yes. Pass the `office` attribute with either the office name or its numeric ID, for example `[corelisting_listings office="Denver"]`. It works on all three shortcodes.

= How quickly do changes in Core Listing appear on my site? =

Within seconds, once you have clicked "Register Webhook with Core Listing". Core Listing then pushes a signed event on every listing change and the plugin clears its cache immediately. Without the webhook, changes appear when the cache expires, which defaults to 15 minutes and is configurable.

= Can I change which fields each card shows? =

Yes, in Core Listing rather than in WordPress. Go to Account Settings and then Listing Display, pick the heading, the sub-line and the detail rows, and rename any label. The same screen has one brokerage-wide choice of whether the details sit below the photo or beside it. You can save a different field list for each property type, so multifamily can lead with unit count while retail leads with rent, and you can give one listing its own layout from the Card Display section of the listing editor. The plugin follows automatically on the next cache refresh, or immediately if you have registered the webhook.

= Can I change how the listings look? =

Yes. Every element carries a stable CSS class, so you can restyle the grid entirely from Appearance to Customize to Additional CSS. The Settings to Core Listing screen lists the class names and shows example rules.

= Does the plugin work with the block editor? =

Yes. All three shortcodes are also registered as blocks under the widgets category, with sidebar controls for every filter and layout option and a live server rendered preview in the editor.

= Does the plugin collect any data about my visitors? =

The plugin sets no cookies, adds no tracking scripts and never transmits visitor information itself. The exception is a page using `[corelisting_search]` with a Google Maps key saved: the visitor's browser loads the map from Google, which sees the visitor's IP address as with any embedded map. See the "External services" section above for the complete list.

= Listings are not showing up. What should I check? =

Log in as an administrator and view the page. When something is misconfigured the plugin prints a short diagnostic, visible only to administrators, showing the URL it called and the HTTP status it received. Then confirm the Core Listing URL and API key on the settings screen and click "Test Connection".

== Screenshots ==

1. The shortcode or block rendered on the front end: a responsive grid of listing cards showing the photo and the fields chosen for that property type in Core Listing.
2. The Settings to Core Listing screen: URL, API key, Test Connection, Register Webhook and Clear Cache controls.
3. The "Core Listing Listings" block with its Inspector sidebar of filter and layout controls.
4. Core Listing, Integrations to WordPress: generate a scoped API key and review connected sites.

== Changelog ==

= 1.5.7 =
* With `hover_popup="yes"` in `[corelisting_search]`, clicking a listing card opens the listing, since hovering already shows its map popup.
* Tested against WordPress 7.1.

= 1.5.6 =
* Shortcode output no longer picks up stray line breaks and empty paragraphs on sites whose theme, page builder or another plugin runs automatic paragraph formatting over it.

= 1.5.5 =
* Sold listings in `[corelisting_search]` are listed after all other listings, whatever sort order is chosen. Set `sold_last="no"` to sort them in with everything else.

= 1.5.4 =
* Sold listings in `[corelisting_search]` never show their asking price. Cards and map popups show the Custom Sold Price Text if set, otherwise the Sold Price, otherwise no price at all. Price per SF, per unit and per lot SF are left off sold listings, since they come from the asking price. Price sorting and the price filter use the sold price for sold listings.

= 1.5.3 =
* `[corelisting_search]` status badges can be recoloured with `status_color_for_sale`, `status_color_for_lease`, `status_color_under_contract`, `status_color_coming_soon`, `status_color_sold` and `status_color_off_market`.
* New `pin_color_by_status="badge"` makes each map pin match its card's badge colour. `pin_color_by_status="yes"` still gives pins their own colours, separate from the badges.
* Sold listings now carry a Sold badge. They previously showed For Sale or For Lease.

= 1.5.2 =
* Fixed: sold listings never appeared in `[corelisting_search]`. Marking a listing sold in Core Listing also closes it, and the search's default `hide_closed` removed every closed listing before `show_sold` was considered. Sold listings with "List on Sold Page" ticked now show, the same rule as your Sold page, and other closed listings stay hidden.

= 1.5.1 =
* `[corelisting_search]` cards and map popups now show the fields you chose in Core Listing under Account Settings then Listing Display, the same as `[corelisting_listings]`. Set `card_fields="fixed"` to keep the fixed price, name, address and chips layout instead.

= 1.5.0 =
* New `[corelisting_search]` shortcode: a listings search with a map beside the list. Keyword, status, sale or lease, property type, price, size, year built and featured filters, plus sorting, all instant with no page reload. In split view the list follows the map and loads more as the visitor scrolls.
* Every control and behaviour is an attribute: each filter can be on, off, or hidden on mobile only; card layouts beside the map and on mobile; map style, pin colour or colour by status; hover popups; whether sold and closed listings appear; the default status and sort.
* A `width` attribute (`content`, `wide` or `full`) lets the search break out of a theme's narrow content column, so the map has room.
* New Google Maps API Key field on the settings screen, used by the search map. Without a key the search runs as a list.
* Pages using the search load Google Maps, and OpenStreetMap for listings with no coordinates, in the visitor's browser. Both are described under External services.

= 1.4.1 =
* Status banners such as "UNDER CONTRACT" and "SOLD" no longer lose an edge on the card. Photo boxes now use the ratio Core Listing bakes a banner at, so nothing is cropped off it.
* Cards set to "beside the photo" now use a square photo. That photo previously stretched to however tall the detail rows came out, so it had no fixed shape and a banner was cropped differently on every card.
* If your theme sets its own `aspect-ratio` on `.corelisting-photo`, banners will still lose their edges there. Remove the override to fix it.

= 1.4.0 =
* The shortcodes are now `[corelisting_listings]`, `[corelisting_sold]` and `[corelisting_members]`. They were `[crm_listings]`, `[crm_sold]` and `[crm_members]`, names generic enough to collide with another plugin on the same site. Any page still using an old name needs editing; the blocks are unchanged and keep working.

= 1.3.1 =
* Corrected the description of the new card arrangement: it is one brokerage-wide setting in Core Listing, not a per property type or per listing one. No change to how listings render.

= 1.3.0 =
* Listing cards can now show the details beside the photo instead of below it. Choose it once for your whole brokerage in Core Listing under Account Settings, Listing Display; this grid follows on the next cache refresh.
* Cards arranged that way carry a `.corelisting-card-side` class, and their grid carries `.corelisting-grid-side`, so a theme can restyle the arrangement.
* A grid of side by side cards uses two columns by default, since each card needs about twice the width. An explicit `columns` attribute is still honoured as written.
* On phones the details always fall back below the photo.

= 1.2.1 =
* Updating the plugin now clears its own listing cache. Previously an upgrade kept serving listings cached by the previous version for up to fifteen minutes, so a release that changed the card could look like it had not installed until you cleared the cache by hand.

= 1.2.0 =
* Listing cards now render the layout you configure in Core Listing under Account Settings, Listing Display, including a different layout per property type. Change it there and this grid follows on the next cache refresh.
* New CSS classes for the configured rows: `.corelisting-subhead`, `.corelisting-details`, `.corelisting-detail`, `.corelisting-detail-label`, `.corelisting-detail-value`, plus a per-field class such as `.corelisting-detail-price` on every row.
* Accounts on an older Core Listing release keep the previous card, so nothing changes until the layouts are available.

= 1.1.1 =
* Removed the Plugin URI header. It pointed at the same address as the Author URI, which the plugin directory rejects. The homepage describes the author, so only Author URI is kept.

= 1.1.0 =
* Prepared the plugin for the WordPress.org plugin directory: added the full GPLv2 license text, a License URI header, a Plugin URI header and a complete readme with an external services disclosure.
* Settings screen JavaScript is now enqueued as a proper script file instead of being printed inline, and is loaded only on the plugin's own settings page.
* Added a "Data sent to Core Listing" summary to the settings screen so the integration's network activity is visible where it is configured.
* Deactivating the plugin no longer contacts Core Listing. Only deleting the plugin releases the webhook registration, since deactivation is usually temporary.
* Text domain is now `core-listing` so translations load correctly from WordPress.org.
* Tested against WordPress 7.0.

= 1.0.18 =
* Settings screen now documents the `show_bio` attribute for `[crm_members]`, with an example.

= 1.0.17 =
* Team member cards now show the member's biography, set on the Core Listing team page for members flagged "Show on Team Page". Hide them with `[crm_members show_bio="no"]` or the "Show biographies" toggle on the block.

= 1.0.16 =
* `[crm_members]` and the Team Members block accept an `office` attribute (name or numeric ID) to scope the roster to one office.
* Documented that `[crm_sold]` accepts the same `office` filter as `[crm_listings]`.

= 1.0.15 =
* Plugin is now listed as "Core Listing" on the Plugins screen.
* Saving settings immediately re-fetches the account's confirmed white label domain, so the effective listing host is correct right away.

= 1.0.14 =
* Settings guidance now recommends entering your confirmed white label domain so listing images are served from your own host.
* All user facing copy now spells the product "Core Listing".

= 1.0.13 =
* Listing card links automatically use the account's confirmed white label custom domain when one is on file.
* Settings screen shows the effective listing URL host actually in use.
* The white label domain is cached locally and refreshed daily, so rendering makes no extra API call.

= 1.0.11 =
* Added license gated automatic updates for self hosted installs.

= 1.0.0 =
* Initial release.
* `[crm_listings]` shortcode and block, server rendered with no build step.
* Settings screen with Test Connection and webhook registration.
* REST webhook receiver with HMAC-SHA256 signature verification.
* Local transient cache, 15 minutes by default, flushed automatically on listing webhooks.

== Upgrade Notice ==

= 1.5.7 =
With hover_popup on, clicking a search card opens the listing. Tested with WordPress 7.1.

= 1.5.6 =
Fixes stray line breaks and empty paragraphs some themes added to listings, search and team output.

= 1.5.5 =
Sold listings now appear at the end of the search results.

= 1.5.4 =
Sold listings in the search show their sold price instead of the asking price.

= 1.5.3 =
Adds colour options for the search's status badges, a pin mode that matches them, and a Sold badge for sold listings.

= 1.5.2 =
Fixes sold listings not showing in [corelisting_search]. Tick "List on Sold Page" on a sold listing in Core Listing to show it.

= 1.5.1 =
Search cards now follow your Listing Display settings in Core Listing. Add card_fields="fixed" to a [corelisting_search] shortcode to keep the previous card.

= 1.5.0 =
Adds the [corelisting_search] shortcode, a map and list search. Enter the Google Maps API key from Core Listing under Settings to Core Listing to turn the map on. Existing shortcodes are unchanged.

= 1.4.0 =
Breaking change: the shortcodes are renamed to [corelisting_listings], [corelisting_sold] and [corelisting_members]. Edit any page still using the old [crm_...] names after updating. Blocks are unaffected.

= 1.3.1 =
Wording fix in the plugin description only. No functional change.

= 1.3.0 =
Listing cards can now place the details beside the photo, set once for your whole brokerage in Core Listing. Existing layouts keep the details below the photo, so nothing changes until you choose otherwise.

= 1.2.1 =
Upgrades now clear the plugin's listing cache automatically, so a new card layout appears immediately instead of after the cache expires.

= 1.2.0 =
Listing cards now follow the layout configured in Core Listing, per property type. Custom CSS that targeted .corelisting-price, .corelisting-address, .corelisting-type or .corelisting-stat needs updating to the new row classes.

= 1.1.1 =
Header fix only. No functional change.

= 1.1.0 =
Directory readiness release. Deactivating the plugin no longer contacts Core Listing, and the text domain changed to core-listing. No settings or shortcodes change.
