=== Ultimate Classified Listings – Classifieds, Directory & Marketplace ===
Contributors: webcodingplace
Tags: classifieds, directory, listings, classified ads, marketplace
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 2.3
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Lightweight classifieds & directory plugin: frontend submission, AJAX search, maps with "near me", SEO schema and sharing. Free, no API key.

== Description ==

**Ultimate Classified Listings turns any WordPress site into a classifieds, directory or marketplace website — without the bloat, and without paying for the basics.** Frontend listing submission, AJAX search with filters, maps with a "near me" radius, unlimited custom fields and a seller dashboard are all included in the free core. A 60-second setup wizard creates every page for you and can even load sample listings so you have something to look at from minute one.

It stays deliberately **lightweight**. There is no page-builder dependency and no heavy framework loaded on every page — assets load only where a listing, search or dashboard is actually rendered. That keeps front-end pages fast and makes the plugin easy to drop onto an existing theme.

Whether you're building a Craigslist-style classifieds board, a local business directory, a real-estate site, a jobs board, an automotive marketplace or a community "buy & sell" section, you get a complete, secure, mobile-friendly listing workflow out of the box — and everything advertised here works in the free version.

= Why choose Ultimate Classified Listings =

* **Free where the big plugins paywall.** Maps, AJAX search, frontend submission, custom fields, SEO structured data and social sharing are all in the free core — not locked behind an add-on bundle.
* **No API keys to chase.** Interactive maps and "near me" radius search run on OpenStreetMap (Leaflet) out of the box. Prefer Google Maps? Add a key and switch — your choice, not a requirement.
* **SEO-ready out of the box.** Every listing automatically outputs schema.org Product, Offer and Breadcrumb structured data (JSON-LD) plus Open Graph / Twitter Card tags, so listings are eligible for Google rich results and look great when shared. It steps aside politely when a dedicated SEO plugin is already handling social meta.
* **Lightweight & fast.** Scoped CSS/JS, conditional asset loading, and no bloated builder to power your listings.
* **Security-first.** CSRF nonces on every AJAX endpoint, capability checks on every action, hashed passwords, email-injection protection and rate-limited auth flows.
* **Theme & builder friendly.** Shortcodes work in Gutenberg, Elementor, Divi and any builder, and every template can be overridden from your theme.
* **Developer friendly & multilingual.** 40+ hooks and filters, REST API support on the listing post type, and WPML / Polylang awareness.

= Core features (all free) =

* Frontend listing submission with custom field sections, image galleries, video and maps
* Seller dashboard — add, edit, delete and manage listings, plus profile management
* Unlimited custom fields and field sections via a drag-and-drop builder
* Categories and tags with custom icons / images
* AJAX-powered search with filters, price range, custom-field filters and sort — three search-form styles
* **Map search** — matching results plotted on a clustered map with a "Near me" / radius filter
* **SEO structured data** — automatic Product, Offer & Breadcrumb JSON-LD + Open Graph / Twitter cards
* **Social sharing** — Facebook, X, WhatsApp, LinkedIn and email buttons on every listing (no third-party scripts or tracking)
* **Related listings** — same-category listings shown on each detail page to keep visitors browsing
* **Featured listings** — promote any listing with a badge and float it to the top of results; one-click star toggle in the admin
* **Favorites** — visitors can save listings and revisit them from the dashboard
* **Listing comparison** — side-by-side compare table
* **One-click demo importer** — seed realistic sample listings for your chosen site type from the setup wizard
* Google Maps **or** OpenStreetMap (Leaflet) with per-listing location markers
* Gallery slider or grid-with-popup, plus card image, corner-radius and colour controls
* Two seller-registration modes (auto-approve or manual approval) and two submission modes (publish or pending review)
* Contact-seller form with reCAPTCHA and email-injection protection
* Dark mode (off / auto / toggle / force)
* Four listing card styles, three single-listing styles
* Full theme template overrides under `/your-theme/ucl/...`

== Installation ==

1. In the WordPress dashboard go to **Plugins → Add New**.
2. Search for **Ultimate Classified Listings**.
3. Click **Install Now**, then **Activate**.
4. The **Setup Wizard** launches automatically — pick your site type, and it creates the Listings, Search, Categories and Seller Dashboard pages for you (and can load sample listings).
5. Fine-tune anything under **Ultimate Classified Listings → Settings**.

== Frequently Asked Questions ==

= Is this plugin really free? =

Yes. Everything described on this page — maps, AJAX search, frontend submission, custom fields, favorites, comparison, SEO schema, social sharing and related listings — is in the free core. There is no paywall on the core workflow.

= Is it good for SEO? =

Yes. As of 2.2 each listing automatically outputs schema.org **Product**, **Offer** and **BreadcrumbList** structured data (JSON-LD) plus **Open Graph** and **Twitter Card** meta tags. That makes listings eligible for Google rich results (price, image, breadcrumbs) and gives them a rich preview when shared on social media. If you run a dedicated SEO plugin (Yoast, Rank Math, SEOPress, All in One SEO), the social meta is skipped automatically to avoid duplicates, while the classified-specific Product schema is still added. You can turn any of it off under **Settings → SEO & Sharing**.

= Does it work with Elementor, Gutenberg or Divi? =

Yes. The plugin is shortcode-based, so its listing, search, categories and dashboard blocks drop into Gutenberg, Elementor, Divi or any other builder. Every template can also be overridden from your theme.

= Do I need a Google Maps API key? =

No. Maps and "near me" radius search run on OpenStreetMap (Leaflet) with no key required. If you prefer Google Maps, add your API key in Settings and switch.

= Is it lightweight? =

Yes — that's a core design goal. There's no page-builder dependency and assets load only on pages that actually render a listing, search or dashboard, so the rest of your site stays untouched.

= Can I promote or highlight specific listings? =

Yes. As of 2.3 you can mark any listing as **featured** — from the Promotion box on its edit screen, or with the one-click star toggle in the Listings admin table. Featured listings get a badge on their card and detail page and, by default, float to the top of listing and search results (the visitor's chosen sort still applies within each group). You can build a dedicated featured section anywhere with `[uclwp_listings featured="only"]`, and turn any of this off under Settings → Featured Listings.

= Can I let users pay to post listings? =

Built-in payment gateways and listing packages are on the roadmap. Today you can require admin approval before listings go live via **Settings → Submission Mode**.

= How are seller passwords stored? =

Passwords are never stored in plain text. Sellers awaiting manual approval have only a salted hash stored; legacy entries from very old versions are routed through the standard WordPress password-reset flow on approval.

= How does the plugin protect against CSRF and abuse? =

Every AJAX endpoint verifies a nonce via `check_ajax_referer()` (separate public and admin nonces), every action runs a capability check, and login, registration and contact-seller endpoints are rate-limited.

= Can I try it with sample content? =

Yes. The setup wizard offers a one-click "Load sample listings" option that seeds realistic listings — with categories, prices and map locations — tailored to the site type you pick. Remove them all again in one click from the wizard's final screen whenever you're ready.

= How do I show listings on a map? =

Add the `[uclwp_search_map]` shortcode to any page (the auto-created Search page includes it), or enable it inline with `[uclwp_search_results map="enable"]`. The map plots every matching listing that has coordinates, clusters nearby pins, and offers a "Near me" button plus a radius selector.

== Screenshots ==

1. A modern, responsive classified-listings grid — the front page your visitors land on.
2. Powerful AJAX search with price range, custom-field filters and instant results.
3. Single-listing page with image gallery, details, seller contact and social sharing.
4. Frontend submission form — sellers post listings without touching wp-admin.
5. Seller dashboard for managing, editing and tracking listings.
6. Map search with clustered pins and a "Near me" radius filter.
7. Drag-and-drop Fields Builder for unlimited custom fields and sections.
8. Setup wizard with one-click page creation and demo-content importer.

== Changelog ==

= 2.3 =

- Feature: In-dashboard documentation. A new "Docs" menu (under Ultimate Classified Listings, just before the Setup Wizard) lists every shortcode in plain language — what each one does, its attributes and defaults, copy-ready examples, and which page it belongs on — so anyone can understand how the plugin works without leaving wp-admin.
- Feature: Featured (promoted) listings. Mark any listing as featured and it gets a "Featured" badge on its card and single-listing page, and — by default — floats to the top of listing and search results while the visitor's chosen sort still applies within the featured and non-featured groups.
- Feature: One-click star toggle in the wp-admin Listings table plus a "Promotion" checkbox on the listing edit screen, so admins can feature/un-feature without opening each listing. The AJAX toggle is nonce-verified and capability-checked (`edit_post`).
- Feature: `[uclwp_listings featured="only"]` renders a dedicated featured-listings section anywhere; `featured="first"` floats featured to the top of any `[uclwp_listings]` query.
- Feature: New Settings → Featured Listings panel — enable/disable the feature, toggle "show featured first", and customise the badge text.
- Developer: featured state is stored as post meta `ucl_featured` = '1' (deleted when un-featured). New action `uclwp_listing_featured_changed`; ordering helper `UCLWP_Featured::apply_featured_first()` is reusable and idempotent.
- Backward compatible: a listing with no featured flag behaves exactly as before; featured-first ordering never excludes any listing (it LEFT JOINs the flag), and the whole feature is off in one setting.

= 2.2 =

- Feature: SEO structured data. Every single-listing page now automatically outputs schema.org **Product** + **Offer** and **BreadcrumbList** structured data as JSON-LD, making listings eligible for Google rich results (price, image, availability, breadcrumbs). Price is read from the listing's `ucl_regular_price` meta and currency from Settings; `condition` maps to schema `itemCondition` when set. Filterable via `uclwp_seo_product_schema` and `uclwp_seo_breadcrumb_schema`.
- Feature: Open Graph + Twitter Card meta on single listings, so a shared listing renders a rich preview (title, description, image, price) on Facebook, X, WhatsApp, Slack, etc. Suppressed automatically when a major SEO plugin (Yoast, Rank Math, SEOPress, All in One SEO, The SEO Framework, Slim SEO) is active and already emitting social tags — no duplicate tags. Filter: `uclwp_seo_output_social`.
- Feature: Social sharing row on single listings — Facebook, X, WhatsApp, LinkedIn and email buttons built entirely from plain links. No third-party scripts, pixels or tracking. Networks are filterable via `uclwp_share_networks`.
- Feature: Related listings. Each listing detail page can show up to N other published listings from the same category (rendered with your chosen card style), which keeps visitors browsing and strengthens internal linking for SEO. Configurable count and heading; query filterable via `uclwp_related_listings_args`.
- Feature: New **Settings → SEO & Sharing** panel to toggle structured data/social meta, social share buttons and related listings, and to set the related-listings count and heading. All three features default to enabled but are purely additive.
- Compatibility: "Tested up to" bumped to WordPress 7.1.
- All 2.2 features are backward compatible: existing installs simply gain `<head>` schema/meta and two new single-page blocks, each individually removable in Settings, with theme-overridable, filterable output.

= 2.1 =

- Feature: One-click demo importer. The setup wizard now offers a "Load sample listings" option that seeds a small set of realistic, published listings for the chosen site type (classifieds, real estate, jobs, local services or automotive). Each sample carries a category, price, custom fields and real map coordinates deliberately clustered across several metros so the new map view has something to show immediately. Every imported post is flagged with `_ucl_sample` and tracked in the `uclwp_sample_ids` option; a "Remove sample listings" button on the wizard's final screen deletes the whole set in one click. No external images are fetched, so the importer works on sites with no outbound network.
- Feature: Map-based search results with clustering. New `[uclwp_search_map]` shortcode renders the listings matching the current search on an interactive Leaflet map, using the bundled `leaflet.markercluster` library to group nearby pins. Each pin opens a popup with the listing thumbnail, title, city, price and a "View listing" link. The map reads the same query args as `[uclwp_search_results]`, so it honours the active filters. Also available inline via a new `map="enable"` attribute on `[uclwp_search_results]` (with `map_height`). The auto-created Search page now includes the map between the form and the results.
- Feature: "Near me" / radius search. The map's optional toolbar adds a geolocation button and a radius selector (km). Choosing a radius reloads with `ucl_lat` / `ucl_lng` / `ucl_radius` query args, and the search query now restricts results to a bounding box around that point (coordinates cast as `DECIMAL` so fractional degrees aren't truncated). Radius presets are filterable via `uclwp_search_map_radius_presets`, the value via `uclwp_search_radius_km`, and the marker cap via `uclwp_search_map_max_markers`.
- Both features are backward compatible: existing installs see no change until they enable the map shortcode/attribute or run the importer, and the map template is theme-overridable at `/your-theme/ucl/shortcodes/search/map.php`.

= 2.0 - 14 June 2026 =

- Bug fix: When the archive/search-results top bar was rendered (via `[uclwp_search_results top_bar="enable"]` or anywhere the `uclwp_archive_topbar` action fires) and a visitor changed the sort dropdown, the resulting URL kept only `sort_by` and `layout` and dropped every other active query arg — keywords, price range, custom-field filters, etc. The top bar now re-emits every other `$_GET` value as a hidden input inside the form (using PHP bracket notation for nested sub-arrays like `regular_price[min]`), so the sort round-trip preserves the active filter set. The grid/list switcher anchors already used `add_query_arg()` and were unaffected.
- Added: New helper `uclwp_flatten_query_args()` that flattens nested arrays into `name[sub][sub]` / value pairs suitable for hidden form inputs. Reusable from any template that needs to pass current GET state through a `<form method="GET">`.

= 1.16.0 =

- Feature: Two new single-listing styles. Pick one in Settings → Templates Settings → Single Listing Style. Style 1 (Classic, default) is the existing look. Style 2 (Card Stack) renders each section as a raised card with a coloured left rail and a chip-style heading — e-commerce / property-listing feel. Style 3 (Minimal) drops the card chrome entirely and separates sections with thin top dividers. Implemented as a body class (`ucl-listing-style-1/2/3`) added on single-listing pages; templates are unchanged so all theme overrides keep working.
- Feature: Price quick-pick chips are now editable from the admin. New "Price Quick-Pick Presets" textarea in Settings → Search Settings (right after Total Listings) — one numeric preset per line; each becomes a "Up to {price}" chip under the price range control in search styles 2 and 3. Leave blank to hide the chips entirely. Still filterable in code via `uclwp_search_price_chips`.
- Feature: `[uclwp_search_results]` now supports a `top_bar="enable"` attribute (default `disable`) that adds the same sort + grid/list switcher the `[uclwp_listings]` shortcode renders.
- Feature: Per-listing map zoom level is now saved. The admin metabox map writes the user-chosen zoom into a hidden `ucl_listing_zoom` input (Leaflet `zoomend` and Google `zoom_changed` events both wired up); both the admin metabox save path and the frontend create/edit handler persist it; the single-listing template reads it back and the map opens at exactly that zoom on the frontend.
- Improvement: Search style 2 polish — reduced cell min-height from 46px to 38px, button is now a properly sized pill (not a tall circle), inputs no longer have huge top/bottom padding. New `columns` shortcode attribute (1–6, default 4) controls how many secondary-field cells appear per row via CSS grid. Mobile collapses to 2 columns at ≤768px and 1 column at ≤480px.
- Bug fix: Card image aspect ratio (Settings → Listings → Card Image Aspect Ratio) was not being applied. The CSS selector keyed off `[style*="--ucl-card-image-ratio"]`, which never matched because the variable is emitted via an inline `<style>` tag, not the element's `style` attribute. The aspect-ratio rule now applies directly to `.uclwp-grid-box-wrap .uclwp-image` and `.uclwp-list-box-wrap .uclwp-image`, so all four card styles honour the chosen ratio.

= 1.15.0 =

- Feature: Two new search-form styles, selectable via `[uclwp_search_form style="2"]` and `[uclwp_search_form style="3"]`.
  - **Style 2 — Bar**: Compact horizontal pill bar with rounded inputs, designed to sit above an archive grid. Trulia/Zillow feel.
  - **Style 3 — Hero**: Tall hero panel with a prominent keyword box, neat secondary-field grid below, and a final CTA row with a "Clear filters" link. Designed to anchor landing or category-archive pages. Configurable heading + sub-heading via the new `search_form_heading` / `search_form_subheading` settings.
- Feature: New `uclwp_render_price_range_search()` helper renders a modern two-input price control with the currency symbol affixed inside each input, plus optional quick-pick chips ("Up to $100", "Up to $500", etc.). Used by styles 2 and 3. Preset chip values are filterable via `uclwp_search_price_chips`.
- Improvement: Equal-height listing cards. Cards within the same row now stretch to match the tallest neighbour and push price/CTA footer rows to the bottom, so grids look tidy even when titles/excerpts vary in length. Scoped to `.ucl-display-listings` so single-listing sidebars and the compare box are unaffected.
- Bug fix: `[uclwp_categories]` (and the underlying `render_category_image()`) no longer renders the saved Bootstrap-icon class. The previous wrapper produced `class="bi bi-{bi bi-house}"`, an invalid class that resolved to nothing. Output is now `class="bi bi-house"` (or the admin-set value). Categories with a saved icon will start rendering it immediately on update.
- Bug fix: Long seller email addresses were overflowing the contact box in the single-listing sidebar. The `<li>` rows now apply `overflow: hidden; text-overflow: ellipsis; white-space: nowrap;` and pick up the missing right padding so the email truncates inside the bordered chip.

= 1.14.2 =

- Bug fix: When creating a new listing in wp-admin, Leaflet threw `Invalid LatLng object: (undefined, undefined)` and the map widget failed to render. Root cause was the v1.14.1 admin-enqueue fix, which preloaded the frontend single-listing map script (`assets/js/location.js`) on the post-edit screen. That script expects a different localize payload (`ucl_location_settings.latitude/longitude`) than the metabox renderer provides (`ucl_map_settings.def_lat/def_long`), so its values came up undefined. The redundant enqueue has been removed — the location field renderer enqueues the correct script (`assets/fields/location.js`) on its own when the section actually renders.
- Bug fix: Belt-and-braces hardening of the location renderer so that even if a stored listing has empty latitude/longitude meta the JS still receives a numeric default (US geographic centre, 37.0902 / -95.7129). The previous longitude default lost its negative sign so the placeholder pin showed up in central Asia instead of Kansas.
- Bug fix: `Control.Geocoder.js` now declares `ucl-leaflet-js` as a dependency so the geocoder loads after Leaflet on the post-edit screen.

= 1.14.1 =

- Bug fix: The Listing Information metabox on the wp-admin post-edit screen (`post.php` / `post-new.php` for the `uclwp_listing` CPT) was rendering without Bootstrap CSS, the icon font, the iconpicker JS, the media uploader, or the map widget. The `load_admin_scripts()` enqueue gate previously matched only the plugin's own subpages (slug contained "uclwp"); it now also enqueues all required assets when the current admin screen's `post_type` is `uclwp_listing`. Bootstrap stays scoped to `.uclwp-bs-wrapper` so it does not affect the surrounding wp-admin UI.

= 1.14.0 =

Final WordPress.org Plugin Check pass — clears all reported errors for plugin-directory submission.

- Plugin name: Renamed to "Ultimate Classified Listings – Classifieds, Directory & Marketplace" so the readme title matches the plugin header and the word "Plugin" (a restricted term) is no longer part of the displayed name.
- Tested up to: bumped to 7.0 to match the current WordPress release as required by .org.
- Escaping: Wrapped the dynamic CSS variable block emitted by `assets/css/styles.php` through `wp_strip_all_tags()` after each value is individually escaped via `esc_attr()`, with phpcs:ignore annotations explaining why the final concatenation is safe.
- Escaping: `paginate_links()` output in `render_pagination()` now passes through `wp_kses_post()` before echoing.
- i18n: Added `/* translators: %s ... */` comments above two `printf( esc_html__( ... ), ... )` calls in the setup-wizard "next steps" panel that were missing them.
- Sanitization: Added `sanitize_text_field( wp_unslash( ... ) )` wrappers around `$_REQUEST['listing_id']` reads in `class-shortcodes.php delete_listing`, `class-email.php contact_seller`, `class-favorites.php ajax_toggle`, and `shortcodes/dashboard/edit.php`. The values are still validated through `uclwp_validate_listing_id()`.
- Sanitization: `$_SERVER['REQUEST_URI']` used to build the favorites login-redirect URL now passes through `esc_url_raw( wp_unslash( ... ) )` before `home_url()`.
- Sanitization: Recursive array inputs (`$_POST['uclwp_data']`, `$_REQUEST['sections']`, `$_REQUEST['fields']`) now have phpcs:ignore annotations documenting that each leaf is sanitized further down. The actual per-leaf sanitization was already in place.
- Sanitization: Login/register password reads carry phpcs:ignore annotations explaining why passwords must not be passed through `sanitize_text_field()` (would strip control characters needed for hash comparison).
- File uploads in seller registration carry phpcs:ignore annotations pointing to the nonce check at the top of the handler.

= 1.13.0 =

Second WordPress.org Plugin Check pass — addresses the warnings flagged by the official PCP tool that the manual sanitization audit missed.

- Plugin Check: Added `defined( 'ABSPATH' ) || exit;` guard to 32 files that didn't have one, including all template files, shortcode partials, admin subviews, the inc/arrays/ data files and assets/css/styles.php.
- Plugin Check: Replaced all `json_encode( $resp )` AJAX response calls with `wp_send_json( $resp )` so character encoding follows WP's UTF-8 conventions and the response is properly closed via `wp_die()`.
- Plugin Check: Replaced remaining non-AJAX `json_encode()` calls (Slick / image-grid data-attributes) with `wp_json_encode()`.
- Plugin Check: Added explicit `timeout` argument (10 s) to every `wp_remote_post()` call.
- Plugin Check: Added `rel="noopener noreferrer"` to every `target="_blank"` link in the admin settings help text, the post-update messages, and the setup-wizard "next steps" panel.
- Plugin Check: Removed `load_plugin_textdomain()` — WordPress 4.6+ loads translations automatically for plugins hosted on .org and the call is now flagged as discouraged.
- Plugin Check: Replaced bare `die();` / `die(0);` with `wp_die();` in AJAX handlers.
- Plugin Check: Added missing version arguments to every `wp_enqueue_style` / `wp_enqueue_script` call (used `UCLWP_VERSION` for bundled assets and `null` for third-party CDN scripts whose URL already carries a version key).
- Plugin Check: Renamed three filters that were missing the plugin prefix: `no_results_message` → `uclwp_no_results_message`, `seller_contact_email_message` → `uclwp_seller_contact_email_message`, `raw_uclwp_price` → `uclwp_raw_price`, `formatted_uclwp_price` → `uclwp_formatted_price`.
- Plugin Check: Removed nested `esc_attr( sanitize_text_field( … ) )` double-escape pattern in the search-form field renderer; the value is now sanitized once on input and the existing `esc_attr()` at output point handles HTML-attribute escaping.
- Plugin Check: Google-Maps script URL now built via `add_query_arg()` instead of string concatenation, and routed through HTTPS unconditionally.
- Plugin Check: Direct `$wpdb->update()` call in seller-approve flow now has type-specifier arrays for $where and $data and a justification comment explaining why the WP user-API helpers can't be used (the password is already hashed).

= 1.12.0 =

This release is a full sanitization + escaping pass for WordPress.org plugin review compliance. No behaviour changes; every output path is now explicitly escaped and every input path is unslash + sanitized.

- Sanitization: All `$_GET` / `$_POST` / `$_REQUEST` access across the plugin now runs through `wp_unslash()` before sanitization, matching WordPress core conventions and `WPCS` requirements.
- Escaping: Replaced `esc_attr_e()` / `esc_attr__()` with the correct `esc_html_e()` / `esc_html__()` everywhere the output is HTML body content (labels, table cells, headings, paragraph text). Attribute-context calls (`placeholder=""`, `title=""`, `value=""`, `data-*=""`) remain `esc_attr_e()`.
- Escaping: Replaced `esc_attr()` with `esc_html()` on user-controlled body text — listing titles, seller names, emails, phone numbers, category names, etc.
- Escaping: Textarea content now uses `esc_textarea()` instead of `esc_attr()`.
- Bugfix: `compare-box.php` printed nothing for compare table column headers due to `esc_attr( $label )` (which returns instead of echoing). Now properly `echo esc_html( $label )`.
- Hardening: Removed the second hardcoded reCAPTCHA fallback key in `templates/sidebar/default.php`. CAPTCHA now fails closed when not configured.
- Hardening: `gdpr_message` setting in the contact-seller form is now output via `wp_kses_post()` so admins can include safe HTML formatting (was `esc_attr()` which broke the HTML).
- Hardening: Search query builder (`uclwp_get_search_query`) sanitizes all dynamic `orderby`, `meta_key`, `tag`, and `range` inputs and validates against an allow-list before passing to `WP_Query`.
- Hardening: HTML emails (contact-seller) now `esc_html()` user-controlled fields (client name, email, phone, message, listing title) before HTML interpolation.
- Hardening: Compare AJAX listing IDs cast through `absint()`; meta_key segments through `sanitize_key()`.
- Hardening: `class-shortcodes::update_profile` casts and unslashes every `$_REQUEST` field.
- Hardening: Term-meta image/icon callbacks (`save_category_image`, `updated_category_image`) now check `current_user_can('manage_categories')` and `wp_unslash()` the inputs (core verifies the nonce upstream on the term-edit screen).
- Hardening: Dashboard "My Listings" search query reads `$_GET['uclwp_status']` through an enum allow-list (`publish`, `pending`, `draft`, `future`, `any`).
- Compatibility: `selected()` and `selected( $a, $b )` used in place of inline `($a == $b) ? 'selected' : ''` ternaries where the markup allowed it.
- Compatibility: `(int) $cat->count` and `(int) count_user_posts(...)` casts in `page-sellers.php` and other admin lists in place of `esc_attr()` on integers.

= 1.11.0 =

- Feature: Modern field rendering. Custom fields on single-listing pages now render as icon + label + value cards by default, using each field's saved icon (from the Fields Builder). Two additional display modes are available — "List" (definition-list rows) and "Inline" (compact icon pills). Choose via Settings → Listings → Field Display on Single Listing Pages.
- Feature: Card meta strip redesigned as inline icon pills with the field's icon, replacing the previous flat title-pipe-value rows. Customize which fields appear via the new Card Meta Fields setting (comma-separated keys).
- Feature: Checkbox / features fields now render as accent-coloured chips with check icons, instead of plain bulleted text.
- Feature: Favorites (save listings). Adds a heart button to every listing card. Saved listings are stored per-user and viewable via the new "Saved Listings" item in the seller dashboard or the `[uclwp_favorites]` shortcode. Toggle the feature in Settings → Listings → Favorites.
- Feature: Dark mode. Five options — Off, Auto (follow OS), Toggle (manual button in top-bar), Auto + Toggle, and Force. Six dark-mode colour overrides for surface, card background, card border, heading, body and muted text.
- Improvement: Field-display helpers now resolve safely when a value is empty so cards never render orphan labels.
- Improvement: `uclwp_listing_action_buttons` filter added so add-ons can inject extra card buttons without forking templates.
- Improvement: `uclwp_dashboard_allowed_pages` filter added.

= 1.10.0 =

- Feature: Three new listing card styles. Style 2 — Minimal (flat, bordered, generous whitespace), Style 3 — Bold Overlay (full-bleed image with gradient and meta overlaid), Style 4 — Compact / Magazine (dense layout with prominent CTA). Each ships with both a grid and a list variant. Switch globally from Listings → Listing Card Style, or per-shortcode via `[uclwp_listings style="3"]`.
- Feature: 14 new colour controls under Colors and CSS — Primary, Primary Hover, Secondary, Accent, Heading, Body Text, Muted Text, Card Background, Card Border, Surface, Price, Badge Background, Badge Text, Button Background/Text/Hover. Each is emitted as a CSS custom property on `.uclwp-bs-wrapper` so themes and custom CSS can override them too.
- Feature: Card Corner Radius setting (0–64px).
- Feature: Card Image Aspect Ratio setting (Original / 4:3 / 16:9 / 1:1 / 3:4) — useful when seller-uploaded images vary in dimensions.
- Compat: Existing installs see no visual change. The legacy two-colour palette (Primary / Secondary Color) is preserved and used as the fallback when the new individual settings are blank.

= 1.9.0 =

- Feature: Setup wizard. A short, dismissible welcome screen guides new installs through site type, page creation (Listings, Search, Categories, Seller Dashboard), currency, map provider and submission workflow. Triggered on activation and surfaced via a one-line admin notice until completed.
- Feature: Auto-create essential pages with the correct shortcodes pre-inserted.
- Feature: Polite, non-blocking "leave a review" prompt. Appears only after 7 days + 3 published listings, with Remind Later (14 days) and Permanent Dismiss options.
- Improvement: Admin classes now load only in `is_admin()` context to reduce frontend overhead.

= 1.8.0 =

This is a **major security hardening release**. All sites should update.

- Security: Added CSRF nonce verification to every public AJAX endpoint (`uclwp_search_listing`, `uclwp_seller_login`, `uclwp_seller_register`, `uclwp_create_listing_frontend`, `uclwp_delete_listing`, `uclwp_contact_seller`, `uclwp_compare_listings`).
- Security: Added CSRF nonce verification to admin AJAX endpoints (`uclwp_save_field_sections`, `uclwp_save_custom_fields`, `uclwp_reset_custom_fields`, `wcp_uclwp_save_settings`, `ucl_deny_seller`, `ucl_approve_seller`).
- Security: Pending-seller passwords are no longer stored in plaintext in `wp_options`. New registrations store only a salted hash; legacy entries are routed through the standard password-reset flow on approval.
- Security: Fixed email header injection in the contact-seller form. From: header now sanitized via a dedicated helper, newlines stripped from name, Reply-To added, Cc recipients individually validated.
- Security: Removed the hardcoded reCAPTCHA secret-key fallback. CAPTCHA verification now fails closed when not configured.
- Security: Replaced unvalidated `$_SERVER['REMOTE_ADDR']` use with `filter_var(... FILTER_VALIDATE_IP)`.
- Security: Listing IDs in delete and edit flows are now validated against the `uclwp_listing` post type and authorized via `current_user_can('edit_post'|'delete_post', $id)` instead of manual ownership checks.
- Security: Pending-seller `userindex` is cast to integer and validated against existing keys before use.
- Security: Permalink-settings save path now requires the core `update-permalink` nonce.
- Security: Added transient-based rate limiting on login, registration and contact-seller endpoints.
- Security: Asset URLs now include `UCLWP_VERSION` for cache busting after security updates.
- Bumped minimum WordPress to 6.0 and minimum PHP to 7.4.
- License header reconciled to GPLv2-or-later.

= 1.7 =

- Bug Fixed: Security related issues

= 1.6 =

- Bug Fixed: Security related issues

= 1.5 =

- Bug Fixed: Security issues fixes, notified by Wordfence

= 1.4 =

- Feature Added: Ability to choose category from the frontend
- Feature Added: Ability to assign tags from the frontend
- Bug Fixed: Empty categories are not displaying
- Bug Fixed: Security issues fixes, notified by WPScan Team

= 1.3 =

- Bug Fixed: Security issues fixes, notified by WPScan Team

= 1.2 =

- Bug Fixed: Iframes not saving from frontend

= 1.1 =

- Feature Added: Iframes support in the shortcode field

= 1.0.0 =

- Initial Release

== Upgrade Notice ==

= 2.3 =
Adds Featured (promoted) listings — a badge, one-click admin star toggle, "show featured first" ordering, and a [uclwp_listings featured="only"] section. All free and toggleable. Backward compatible.

= 2.2 =
Adds automatic SEO structured data (Product/Offer/Breadcrumb JSON-LD), Open Graph & Twitter cards, social share buttons and related listings — all free and toggleable. Tested up to WordPress 7.1. Backward compatible.

= 2.1 =
Adds a clustered map view for search results with a "Near me" radius filter, plus a one-click demo-listings importer in the setup wizard. Backward compatible.

= 2.0 =
Fixes the archive top bar wiping active search filters when the sort dropdown changes. Strongly recommended for anyone using [uclwp_search_results top_bar="enable"].

= 1.16.0 =
Adds two new single-listing styles, admin-editable price chips, a top bar option on [uclwp_search_results], per-listing map zoom persistence, a columns attribute on search style 2, and a fix for the card image aspect-ratio setting that wasn't applying.

= 1.15.0 =
Adds two new search-form styles (Bar + Hero) with a modern price-range control. Fixes equal-height grid cards, the empty category icon, and overflowing seller emails in the sidebar.

= 1.14.2 =
Fixes the "Invalid LatLng object" Leaflet error when creating a new listing in wp-admin, introduced by the 1.14.1 metabox-assets fix. Strongly recommended for anyone on 1.14.1.

= 1.14.1 =
Bug fix: restores Bootstrap CSS, icons, media uploader and map on the wp-admin Listing Information metabox. No behaviour change anywhere else.

= 1.14.0 =
Release-candidate compliance pass — clears all Plugin Check errors for WordPress.org submission (escape on dynamic CSS, paginate_links output, translator comments, Tested up to 7.0, plugin name rules). No behaviour change.

= 1.13.0 =
Second WordPress.org Plugin Check compliance pass. Adds ABSPATH guards everywhere, wp_send_json/wp_json_encode adoption, wp_remote timeout, rel="noopener noreferrer" on target=_blank links, removes deprecated load_plugin_textdomain, prefixes orphan filters. No behaviour change.

= 1.12.0 =
Full sanitization and escaping audit for WordPress.org plugin review compliance. Fixes one display bug in the compare-listings table; removes a second hardcoded reCAPTCHA fallback key; no breaking changes.

= 1.11.0 =
Field display redesigned on single pages and cards (icon + label + value cards, definition lists, or inline pills). Adds Favorites and Dark Mode. Visually backward-compatible — the new "cards" display becomes the default on single-listing pages.

= 1.10.0 =
Adds three new listing card styles (Minimal, Bold Overlay, Compact), a 14-colour palette wired through CSS variables, card radius and image aspect-ratio settings. Visually backward-compatible.

= 1.9.0 =
Adds a setup wizard, auto-page creation, and a polite review prompt. Builds on the 1.8.0 security hardening.

= 1.8.0 =
Major security hardening release. Adds CSRF protection to every AJAX endpoint, removes plaintext password storage, fixes email header injection, and rate-limits authentication. All sites should update.
