{"id":160100,"date":"2022-08-01T03:18:07","date_gmt":"2022-08-01T03:18:07","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/ultimate-classified-listings\/"},"modified":"2026-09-30T06:40:04","modified_gmt":"2026-09-30T06:40:04","slug":"ultimate-classified-listings","status":"publish","type":"plugin","link":"https:\/\/kab.wordpress.org\/plugins\/ultimate-classified-listings\/","author":17556873,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.5","stable_tag":"2.5","tested":"7.1.3","requires":"6.0","requires_php":"7.4","requires_plugins":null,"header_name":"Ultimate Classified Listings \u2013 Classifieds, Directory & Marketplace","header_author":"WebCodingPlace","header_description":"A simple yet complete classifieds and listings system for WordPress.","assets_banners_color":"d5d9e3","last_updated":"2026-09-30 06:40:04","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/wpclassifiedlistings.com\/","header_author_uri":"https:\/\/webcodingplace.com\/","rating":5,"author_block_rating":0,"active_installs":40,"downloads":5152,"num_ratings":2,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.1":{"tag":"1.1","author":"webcodingplace","date":"2022-09-02 14:49:31","revision":2779335},"1.2":{"tag":"1.2","author":"webcodingplace","date":"2024-06-28 02:29:20","revision":3109013},"1.3":{"tag":"1.3","author":"webcodingplace","date":"2024-06-29 07:10:23","revision":3109579},"1.4":{"tag":"1.4","author":"webcodingplace","date":"2024-09-01 07:50:27","revision":3144841},"1.5":{"tag":"1.5","author":"webcodingplace","date":"2025-02-16 03:58:12","revision":3241168},"1.6":{"tag":"1.6","author":"webcodingplace","date":"2025-03-06 09:55:00","revision":3251583},"1.7":{"tag":"1.7","author":"webcodingplace","date":"2026-06-14 04:03:46","revision":3571494},"2.0":{"tag":"2.0","author":"webcodingplace","date":"2026-06-14 04:03:46","revision":3571494},"2.1":{"tag":"2.1","author":"webcodingplace","date":"2026-08-20 14:52:21","revision":3657155},"2.3":{"tag":"2.3","author":"webcodingplace","date":"2026-09-12 19:21:17","revision":3693056},"2.4":{"tag":"2.4","author":"webcodingplace","date":"2026-09-17 04:34:29","revision":3699536},"2.5":{"tag":"2.5","author":"webcodingplace","date":"2026-09-30 06:40:04","revision":3720466}},"upgrade_notice":{"2.5":"<p>Adds ratings &amp; reviews for listings, with the aggregate rating fed into Product structured data so listings can show star ratings in Google. Reviews are moderated WordPress comments. Opt-out and backward compatible.<\/p>","2.4":"<p>Adds optional automatic listing expiry \u2014 expire after N days, email sellers before\/after, and one-click renew from the dashboard. Off by default; existing listings are never changed. Backward compatible.<\/p>","2.3":"<p>Adds Featured (promoted) listings \u2014 a badge, one-click admin star toggle, &quot;show featured first&quot; ordering, and a [uclwp_listings featured=&quot;only&quot;] section. All free and toggleable. Backward compatible.<\/p>","2.2":"<p>Adds automatic SEO structured data (Product\/Offer\/Breadcrumb JSON-LD), Open Graph &amp; Twitter cards, social share buttons and related listings \u2014 all free and toggleable. Tested up to WordPress 7.1. Backward compatible.<\/p>","2.1":"<p>Adds a clustered map view for search results with a &quot;Near me&quot; radius filter, plus a one-click demo-listings importer in the setup wizard. Backward compatible.<\/p>","2.0":"<p>Fixes the archive top bar wiping active search filters when the sort dropdown changes. Strongly recommended for anyone using [uclwp_search_results top_bar=&quot;enable&quot;].<\/p>","1.16.0":"<p>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&#039;t applying.<\/p>","1.15.0":"<p>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.<\/p>","1.14.2":"<p>Fixes the &quot;Invalid LatLng object&quot; 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.<\/p>","1.14.1":"<p>Bug fix: restores Bootstrap CSS, icons, media uploader and map on the wp-admin Listing Information metabox. No behaviour change anywhere else.<\/p>","1.14.0":"<p>Release-candidate compliance pass \u2014 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.<\/p>","1.13.0":"<p>Second WordPress.org Plugin Check compliance pass. Adds ABSPATH guards everywhere, wp_send_json\/wp_json_encode adoption, wp_remote timeout, rel=&quot;noopener noreferrer&quot; on target=_blank links, removes deprecated load_plugin_textdomain, prefixes orphan filters. No behaviour change.<\/p>","1.12.0":"<p>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.<\/p>","1.11.0":"<p>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 \u2014 the new &quot;cards&quot; display becomes the default on single-listing pages.<\/p>","1.10.0":"<p>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.<\/p>","1.9.0":"<p>Adds a setup wizard, auto-page creation, and a polite review prompt. Builds on the 1.8.0 security hardening.<\/p>","1.8.0":"<p>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.<\/p>"},"ratings":{"1":0,"2":0,"3":0,"4":0,"5":2},"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3571494,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3571494,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.jpg":{"filename":"banner-1544x500.jpg","revision":3571494,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.jpg":{"filename":"banner-772x250.jpg","revision":3571494,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.1","1.2","1.3","1.4","1.5","1.6","1.7","2.0","2.1","2.3","2.4","2.5"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":2764251,"resolution":"1","location":"assets","locale":"","width":1592,"height":2538},"screenshot-2.png":{"filename":"screenshot-2.png","revision":2764251,"resolution":"2","location":"assets","locale":"","width":1686,"height":687},"screenshot-3.png":{"filename":"screenshot-3.png","revision":2764251,"resolution":"3","location":"assets","locale":"","width":1692,"height":1132},"screenshot-4.png":{"filename":"screenshot-4.png","revision":2764251,"resolution":"4","location":"assets","locale":"","width":1565,"height":3260}},"screenshots":{"1":"A modern, responsive classified-listings grid \u2014 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 \u2014 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."}},"plugin_section":[],"plugin_tags":[4531,4527,2466,4528,8379],"plugin_category":[35,39],"plugin_contributors":[187022],"plugin_business_model":[],"class_list":["post-160100","plugin","type-plugin","status-publish","hentry","plugin_tags-classified-ads","plugin_tags-classifieds","plugin_tags-directory","plugin_tags-listings","plugin_tags-marketplace","plugin_category-advertising","plugin_category-business","plugin_contributors-webcodingplace","plugin_committers-webcodingplace"],"banners":{"banner":"https:\/\/ps.w.org\/ultimate-classified-listings\/assets\/banner-772x250.jpg?rev=3571494","banner_2x":"https:\/\/ps.w.org\/ultimate-classified-listings\/assets\/banner-1544x500.jpg?rev=3571494","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/ultimate-classified-listings\/assets\/icon-128x128.png?rev=3571494","icon_2x":"https:\/\/ps.w.org\/ultimate-classified-listings\/assets\/icon-256x256.png?rev=3571494","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/ultimate-classified-listings\/assets\/screenshot-1.png?rev=2764251","caption":"A modern, responsive classified-listings grid \u2014 the front page your visitors land on."},{"src":"https:\/\/ps.w.org\/ultimate-classified-listings\/assets\/screenshot-2.png?rev=2764251","caption":"Powerful AJAX search with price range, custom-field filters and instant results."},{"src":"https:\/\/ps.w.org\/ultimate-classified-listings\/assets\/screenshot-3.png?rev=2764251","caption":"Single-listing page with image gallery, details, seller contact and social sharing."},{"src":"https:\/\/ps.w.org\/ultimate-classified-listings\/assets\/screenshot-4.png?rev=2764251","caption":"Frontend submission form \u2014 sellers post listings without touching wp-admin."}],"raw_content":"<!--section=description-->\n<p><strong>Ultimate Classified Listings turns any WordPress site into a classifieds, directory or marketplace website \u2014 without the bloat, and without paying for the basics.<\/strong> 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.<\/p>\n\n<p>It stays deliberately <strong>lightweight<\/strong>. There is no page-builder dependency and no heavy framework loaded on every page \u2014 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.<\/p>\n\n<p>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 &amp; sell\" section, you get a complete, secure, mobile-friendly listing workflow out of the box \u2014 and everything advertised here works in the free version.<\/p>\n\n<h4>Why choose Ultimate Classified Listings<\/h4>\n\n<ul>\n<li><strong>Free where the big plugins paywall.<\/strong> Maps, AJAX search, frontend submission, custom fields, SEO structured data and social sharing are all in the free core \u2014 not locked behind an add-on bundle.<\/li>\n<li><strong>No API keys to chase.<\/strong> Interactive maps and \"near me\" radius search run on OpenStreetMap (Leaflet) out of the box. Prefer Google Maps? Add a key and switch \u2014 your choice, not a requirement.<\/li>\n<li><strong>SEO-ready out of the box.<\/strong> 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.<\/li>\n<li><strong>Lightweight &amp; fast.<\/strong> Scoped CSS\/JS, conditional asset loading, and no bloated builder to power your listings.<\/li>\n<li><strong>Security-first.<\/strong> CSRF nonces on every AJAX endpoint, capability checks on every action, hashed passwords, email-injection protection and rate-limited auth flows.<\/li>\n<li><strong>Theme &amp; builder friendly.<\/strong> Shortcodes work in Gutenberg, Elementor, Divi and any builder, and every template can be overridden from your theme.<\/li>\n<li><strong>Developer friendly &amp; multilingual.<\/strong> 40+ hooks and filters, REST API support on the listing post type, and WPML \/ Polylang awareness.<\/li>\n<\/ul>\n\n<h4>Core features (all free)<\/h4>\n\n<ul>\n<li>Frontend listing submission with custom field sections, image galleries, video and maps<\/li>\n<li>Seller dashboard \u2014 add, edit, delete and manage listings, plus profile management<\/li>\n<li>Unlimited custom fields and field sections via a drag-and-drop builder<\/li>\n<li>Categories and tags with custom icons \/ images<\/li>\n<li>AJAX-powered search with filters, price range, custom-field filters and sort \u2014 three search-form styles<\/li>\n<li><strong>Map search<\/strong> \u2014 matching results plotted on a clustered map with a \"Near me\" \/ radius filter<\/li>\n<li><strong>Ratings &amp; reviews<\/strong> \u2014 visitors rate and review listings; the aggregate rating feeds Google's structured data so listings can show star ratings in search results<\/li>\n<li><strong>SEO structured data<\/strong> \u2014 automatic Product, Offer, Rating &amp; Breadcrumb JSON-LD + Open Graph \/ Twitter cards<\/li>\n<li><strong>Social sharing<\/strong> \u2014 Facebook, X, WhatsApp, LinkedIn and email buttons on every listing (no third-party scripts or tracking)<\/li>\n<li><strong>Related listings<\/strong> \u2014 same-category listings shown on each detail page to keep visitors browsing<\/li>\n<li><strong>Featured listings<\/strong> \u2014 promote any listing with a badge and float it to the top of results; one-click star toggle in the admin<\/li>\n<li><strong>Listing expiry<\/strong> \u2014 optionally expire listings after N days, email the seller before\/after, and let sellers renew from their dashboard<\/li>\n<li><strong>Favorites<\/strong> \u2014 visitors can save listings and revisit them from the dashboard<\/li>\n<li><strong>Listing comparison<\/strong> \u2014 side-by-side compare table<\/li>\n<li><strong>One-click demo importer<\/strong> \u2014 seed realistic sample listings for your chosen site type from the setup wizard<\/li>\n<li>Google Maps <strong>or<\/strong> OpenStreetMap (Leaflet) with per-listing location markers<\/li>\n<li>Gallery slider or grid-with-popup, plus card image, corner-radius and colour controls<\/li>\n<li>Two seller-registration modes (auto-approve or manual approval) and two submission modes (publish or pending review)<\/li>\n<li>Contact-seller form with reCAPTCHA and email-injection protection<\/li>\n<li>Dark mode (off \/ auto \/ toggle \/ force)<\/li>\n<li>Four listing card styles, three single-listing styles<\/li>\n<li>Full theme template overrides under <code>\/your-theme\/ucl\/...<\/code><\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>In the WordPress dashboard go to <strong>Plugins \u2192 Add New<\/strong>.<\/li>\n<li>Search for <strong>Ultimate Classified Listings<\/strong>.<\/li>\n<li>Click <strong>Install Now<\/strong>, then <strong>Activate<\/strong>.<\/li>\n<li>The <strong>Setup Wizard<\/strong> launches automatically \u2014 pick your site type, and it creates the Listings, Search, Categories and Seller Dashboard pages for you (and can load sample listings).<\/li>\n<li>Fine-tune anything under <strong>Ultimate Classified Listings \u2192 Settings<\/strong>.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"is%20this%20plugin%20really%20free%3F\"><h3>Is this plugin really free?<\/h3><\/dt>\n<dd><p>Yes. Everything described on this page \u2014 maps, AJAX search, frontend submission, custom fields, favorites, comparison, SEO schema, social sharing and related listings \u2014 is in the free core. There is no paywall on the core workflow.<\/p><\/dd>\n<dt id=\"is%20it%20good%20for%20seo%3F\"><h3>Is it good for SEO?<\/h3><\/dt>\n<dd><p>Yes. As of 2.2 each listing automatically outputs schema.org <strong>Product<\/strong>, <strong>Offer<\/strong> and <strong>BreadcrumbList<\/strong> structured data (JSON-LD) plus <strong>Open Graph<\/strong> and <strong>Twitter Card<\/strong> 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 <strong>Settings \u2192 SEO &amp; Sharing<\/strong>.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20elementor%2C%20gutenberg%20or%20divi%3F\"><h3>Does it work with Elementor, Gutenberg or Divi?<\/h3><\/dt>\n<dd><p>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.<\/p><\/dd>\n<dt id=\"do%20i%20need%20a%20google%20maps%20api%20key%3F\"><h3>Do I need a Google Maps API key?<\/h3><\/dt>\n<dd><p>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.<\/p><\/dd>\n<dt id=\"is%20it%20lightweight%3F\"><h3>Is it lightweight?<\/h3><\/dt>\n<dd><p>Yes \u2014 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.<\/p><\/dd>\n<dt id=\"can%20i%20promote%20or%20highlight%20specific%20listings%3F\"><h3>Can I promote or highlight specific listings?<\/h3><\/dt>\n<dd><p>Yes. As of 2.3 you can mark any listing as <strong>featured<\/strong> \u2014 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 <code>[uclwp_listings featured=\"only\"]<\/code>, and turn any of this off under Settings \u2192 Featured Listings.<\/p><\/dd>\n<dt id=\"can%20visitors%20rate%20and%20review%20listings%3F\"><h3>Can visitors rate and review listings?<\/h3><\/dt>\n<dd><p>Yes, as of 2.5. Enable <strong>Settings \u2192 Reviews &amp; Ratings<\/strong> and each listing page gets a star-rating summary, a list of reviews and a review form. Reviews are stored as WordPress comments, so you moderate them from the normal Comments screen (and Akismet works on them). New reviews are held for moderation by default; you can allow guests or require login. Best of all, the aggregate rating is added to each listing's Product structured data (AggregateRating), so listings with reviews become eligible to show <strong>star ratings in Google search results<\/strong>.<\/p><\/dd>\n<dt id=\"can%20listings%20expire%20automatically%3F\"><h3>Can listings expire automatically?<\/h3><\/dt>\n<dd><p>Yes, as of 2.4. Turn on <strong>Settings \u2192 Listing Expiry<\/strong> and choose how many days a listing stays live after it's published. When a listing expires it's moved to Draft (or Trash) so it disappears from all public views. You can email the seller when it expires and a few days before, and \u2014 if you allow it \u2014 sellers renew expired listings with one click from their dashboard. This is <strong>off by default<\/strong>, and turning it on only affects listings published from that point on; your existing listings are never changed.<\/p><\/dd>\n<dt id=\"can%20i%20let%20users%20pay%20to%20post%20listings%3F\"><h3>Can I let users pay to post listings?<\/h3><\/dt>\n<dd><p>Built-in payment gateways and listing packages are on the roadmap. Today you can require admin approval before listings go live via <strong>Settings \u2192 Submission Mode<\/strong>.<\/p><\/dd>\n<dt id=\"how%20are%20seller%20passwords%20stored%3F\"><h3>How are seller passwords stored?<\/h3><\/dt>\n<dd><p>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.<\/p><\/dd>\n<dt id=\"how%20does%20the%20plugin%20protect%20against%20csrf%20and%20abuse%3F\"><h3>How does the plugin protect against CSRF and abuse?<\/h3><\/dt>\n<dd><p>Every AJAX endpoint verifies a nonce via <code>check_ajax_referer()<\/code> (separate public and admin nonces), every action runs a capability check, and login, registration and contact-seller endpoints are rate-limited.<\/p><\/dd>\n<dt id=\"can%20i%20try%20it%20with%20sample%20content%3F\"><h3>Can I try it with sample content?<\/h3><\/dt>\n<dd><p>Yes. The setup wizard offers a one-click \"Load sample listings\" option that seeds realistic listings \u2014 with categories, prices and map locations \u2014 tailored to the site type you pick. Remove them all again in one click from the wizard's final screen whenever you're ready.<\/p><\/dd>\n<dt id=\"how%20do%20i%20show%20listings%20on%20a%20map%3F\"><h3>How do I show listings on a map?<\/h3><\/dt>\n<dd><p>Add the <code>[uclwp_search_map]<\/code> shortcode to any page (the auto-created Search page includes it), or enable it inline with <code>[uclwp_search_results map=\"enable\"]<\/code>. The map plots every matching listing that has coordinates, clusters nearby pins, and offers a \"Near me\" button plus a radius selector.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.5<\/h4>\n\n<ul>\n<li>Fix: Listings no longer return \"404 \u2013 Not Found\" after a fresh install or update. WordPress' permalink rules are now refreshed automatically once per version (the classic custom-post-type rewrite-flush gotcha). The in-dashboard Docs page also carries a clear notice explaining the one-time Settings \u2192 Permalinks re-save, just in case.<\/li>\n<li>Feature: Ratings &amp; reviews. Each listing page gains a star-rating summary, a reviews list and a review form. Visitors give a 1\u20135 star rating and a short review; you can allow guests (name + email) or require login, and reviews are held for moderation by default (or publish immediately).<\/li>\n<li>Feature: Star ratings in Google. The approved average is added to each listing's schema.org Product markup as AggregateRating (via the existing SEO structured-data output), so listings with reviews are eligible to show star ratings in search results. Toggle under Settings \u2192 Reviews &amp; Ratings.<\/li>\n<li>Reviews are stored as native WordPress comments (comment type <code>uclwp_review<\/code> with a <code>ucl_rating<\/code> meta), so moderation happens on the normal Comments screen and Akismet applies. They are kept out of the site's ordinary comment feeds and Recent Comments widget.<\/li>\n<li>Performance: the approved average and count are cached in post meta (<code>ucl_rating_average<\/code> \/ <code>ucl_rating_count<\/code>) and recomputed only when a review is added, moderated, edited or deleted \u2014 never on page render.<\/li>\n<li>Security: submissions are nonce-verified, rate-limited and honeypot-protected; ratings are clamped to 1\u20135; one review per logged-in user (and per email for guests).<\/li>\n<li>Developer: new action <code>uclwp_review_added<\/code>; the reviews template is overridable at <code>\/your-theme\/ucl\/content-single\/reviews.php<\/code>; helper <code>UCLWP_Reviews::stars_html()<\/code>.<\/li>\n<li>All of the above is opt-out (Settings \u2192 Reviews &amp; Ratings) and backward compatible \u2014 a listing with no reviews is unchanged.<\/li>\n<\/ul>\n\n<h4>2.4<\/h4>\n\n<ul>\n<li>Feature: Automatic listing expiry. Turn it on under Settings \u2192 Listing Expiry and listings expire a configurable number of days after they're published. A daily cron job moves each expired listing to Draft (or Trash), so it drops out of every public listing, search and map view. Off by default and fully backward compatible: enabling it only affects listings published from that point on, and any listing without an expiry date is never touched.<\/li>\n<li>Feature: Per-listing expiry control. A \"Listing Expiry\" box on the edit screen lets you set an exact expiry date or mark a listing \"Never expires\", and an \"Expires\" column in the Listings table shows each listing's date at a glance.<\/li>\n<li>Feature: Seller renewals. Expired listings show a \"Renew\" button in the seller dashboard (when allowed) that gives the listing a fresh expiry period and puts it back online in one click.<\/li>\n<li>Feature: Expiry emails. Optionally email the listing owner when their listing expires, plus a reminder a configurable number of days beforehand. All subjects\/bodies are filterable.<\/li>\n<li>Developer: new meta <code>ucl_listing_expiry<\/code> (Unix timestamp; absent = never), actions <code>uclwp_listing_expired<\/code> and <code>uclwp_listing_renewed<\/code>, and filters on every email string. The daily event is <code>uclwp_expire_listings_event<\/code> and is cleared on deactivation.<\/li>\n<\/ul>\n\n<h4>2.3<\/h4>\n\n<ul>\n<li>Feature: In-dashboard documentation. A new \"Docs\" menu (under Ultimate Classified Listings, just before the Setup Wizard) lists every shortcode in plain language \u2014 what each one does, its attributes and defaults, copy-ready examples, and which page it belongs on \u2014 so anyone can understand how the plugin works without leaving wp-admin.<\/li>\n<li>Feature: Featured (promoted) listings. Mark any listing as featured and it gets a \"Featured\" badge on its card and single-listing page, and \u2014 by default \u2014 floats to the top of listing and search results while the visitor's chosen sort still applies within the featured and non-featured groups.<\/li>\n<li>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 (<code>edit_post<\/code>).<\/li>\n<li>Feature: <code>[uclwp_listings featured=\"only\"]<\/code> renders a dedicated featured-listings section anywhere; <code>featured=\"first\"<\/code> floats featured to the top of any <code>[uclwp_listings]<\/code> query.<\/li>\n<li>Feature: New Settings \u2192 Featured Listings panel \u2014 enable\/disable the feature, toggle \"show featured first\", and customise the badge text.<\/li>\n<li>Developer: featured state is stored as post meta <code>ucl_featured<\/code> = '1' (deleted when un-featured). New action <code>uclwp_listing_featured_changed<\/code>; ordering helper <code>UCLWP_Featured::apply_featured_first()<\/code> is reusable and idempotent.<\/li>\n<li>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.<\/li>\n<\/ul>\n\n<h4>2.2<\/h4>\n\n<ul>\n<li>Feature: SEO structured data. Every single-listing page now automatically outputs schema.org <strong>Product<\/strong> + <strong>Offer<\/strong> and <strong>BreadcrumbList<\/strong> structured data as JSON-LD, making listings eligible for Google rich results (price, image, availability, breadcrumbs). Price is read from the listing's <code>ucl_regular_price<\/code> meta and currency from Settings; <code>condition<\/code> maps to schema <code>itemCondition<\/code> when set. Filterable via <code>uclwp_seo_product_schema<\/code> and <code>uclwp_seo_breadcrumb_schema<\/code>.<\/li>\n<li>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 \u2014 no duplicate tags. Filter: <code>uclwp_seo_output_social<\/code>.<\/li>\n<li>Feature: Social sharing row on single listings \u2014 Facebook, X, WhatsApp, LinkedIn and email buttons built entirely from plain links. No third-party scripts, pixels or tracking. Networks are filterable via <code>uclwp_share_networks<\/code>.<\/li>\n<li>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 <code>uclwp_related_listings_args<\/code>.<\/li>\n<li>Feature: New <strong>Settings \u2192 SEO &amp; Sharing<\/strong> 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.<\/li>\n<li>Compatibility: \"Tested up to\" bumped to WordPress 7.1.<\/li>\n<li>All 2.2 features are backward compatible: existing installs simply gain <code>&lt;head&gt;<\/code> schema\/meta and two new single-page blocks, each individually removable in Settings, with theme-overridable, filterable output.<\/li>\n<\/ul>\n\n<h4>2.1<\/h4>\n\n<ul>\n<li>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 <code>_ucl_sample<\/code> and tracked in the <code>uclwp_sample_ids<\/code> 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.<\/li>\n<li>Feature: Map-based search results with clustering. New <code>[uclwp_search_map]<\/code> shortcode renders the listings matching the current search on an interactive Leaflet map, using the bundled <code>leaflet.markercluster<\/code> 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 <code>[uclwp_search_results]<\/code>, so it honours the active filters. Also available inline via a new <code>map=\"enable\"<\/code> attribute on <code>[uclwp_search_results]<\/code> (with <code>map_height<\/code>). The auto-created Search page now includes the map between the form and the results.<\/li>\n<li>Feature: \"Near me\" \/ radius search. The map's optional toolbar adds a geolocation button and a radius selector (km). Choosing a radius reloads with <code>ucl_lat<\/code> \/ <code>ucl_lng<\/code> \/ <code>ucl_radius<\/code> query args, and the search query now restricts results to a bounding box around that point (coordinates cast as <code>DECIMAL<\/code> so fractional degrees aren't truncated). Radius presets are filterable via <code>uclwp_search_map_radius_presets<\/code>, the value via <code>uclwp_search_radius_km<\/code>, and the marker cap via <code>uclwp_search_map_max_markers<\/code>.<\/li>\n<li>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 <code>\/your-theme\/ucl\/shortcodes\/search\/map.php<\/code>.<\/li>\n<\/ul>\n\n<h4>2.0 - 14 June 2026<\/h4>\n\n<ul>\n<li>Bug fix: When the archive\/search-results top bar was rendered (via <code>[uclwp_search_results top_bar=\"enable\"]<\/code> or anywhere the <code>uclwp_archive_topbar<\/code> action fires) and a visitor changed the sort dropdown, the resulting URL kept only <code>sort_by<\/code> and <code>layout<\/code> and dropped every other active query arg \u2014 keywords, price range, custom-field filters, etc. The top bar now re-emits every other <code>$_GET<\/code> value as a hidden input inside the form (using PHP bracket notation for nested sub-arrays like <code>regular_price[min]<\/code>), so the sort round-trip preserves the active filter set. The grid\/list switcher anchors already used <code>add_query_arg()<\/code> and were unaffected.<\/li>\n<li>Added: New helper <code>uclwp_flatten_query_args()<\/code> that flattens nested arrays into <code>name[sub][sub]<\/code> \/ value pairs suitable for hidden form inputs. Reusable from any template that needs to pass current GET state through a <code>&lt;form method=\"GET\"&gt;<\/code>.<\/li>\n<\/ul>\n\n<h4>1.16.0<\/h4>\n\n<ul>\n<li>Feature: Two new single-listing styles. Pick one in Settings \u2192 Templates Settings \u2192 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 \u2014 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 (<code>ucl-listing-style-1\/2\/3<\/code>) added on single-listing pages; templates are unchanged so all theme overrides keep working.<\/li>\n<li>Feature: Price quick-pick chips are now editable from the admin. New \"Price Quick-Pick Presets\" textarea in Settings \u2192 Search Settings (right after Total Listings) \u2014 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 <code>uclwp_search_price_chips<\/code>.<\/li>\n<li>Feature: <code>[uclwp_search_results]<\/code> now supports a <code>top_bar=\"enable\"<\/code> attribute (default <code>disable<\/code>) that adds the same sort + grid\/list switcher the <code>[uclwp_listings]<\/code> shortcode renders.<\/li>\n<li>Feature: Per-listing map zoom level is now saved. The admin metabox map writes the user-chosen zoom into a hidden <code>ucl_listing_zoom<\/code> input (Leaflet <code>zoomend<\/code> and Google <code>zoom_changed<\/code> 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.<\/li>\n<li>Improvement: Search style 2 polish \u2014 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 <code>columns<\/code> shortcode attribute (1\u20136, default 4) controls how many secondary-field cells appear per row via CSS grid. Mobile collapses to 2 columns at \u2264768px and 1 column at \u2264480px.<\/li>\n<li>Bug fix: Card image aspect ratio (Settings \u2192 Listings \u2192 Card Image Aspect Ratio) was not being applied. The CSS selector keyed off <code>[style*=\"--ucl-card-image-ratio\"]<\/code>, which never matched because the variable is emitted via an inline <code>&lt;style&gt;<\/code> tag, not the element's <code>style<\/code> attribute. The aspect-ratio rule now applies directly to <code>.uclwp-grid-box-wrap .uclwp-image<\/code> and <code>.uclwp-list-box-wrap .uclwp-image<\/code>, so all four card styles honour the chosen ratio.<\/li>\n<\/ul>\n\n<h4>1.15.0<\/h4>\n\n<ul>\n<li>Feature: Two new search-form styles, selectable via <code>[uclwp_search_form style=\"2\"]<\/code> and <code>[uclwp_search_form style=\"3\"]<\/code>.\n\n<ul>\n<li><strong>Style 2 \u2014 Bar<\/strong>: Compact horizontal pill bar with rounded inputs, designed to sit above an archive grid. Trulia\/Zillow feel.<\/li>\n<li><strong>Style 3 \u2014 Hero<\/strong>: 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 <code>search_form_heading<\/code> \/ <code>search_form_subheading<\/code> settings.<\/li>\n<\/ul><\/li>\n<li>Feature: New <code>uclwp_render_price_range_search()<\/code> 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 <code>uclwp_search_price_chips<\/code>.<\/li>\n<li>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 <code>.ucl-display-listings<\/code> so single-listing sidebars and the compare box are unaffected.<\/li>\n<li>Bug fix: <code>[uclwp_categories]<\/code> (and the underlying <code>render_category_image()<\/code>) no longer renders the saved Bootstrap-icon class. The previous wrapper produced <code>class=\"bi bi-{bi bi-house}\"<\/code>, an invalid class that resolved to nothing. Output is now <code>class=\"bi bi-house\"<\/code> (or the admin-set value). Categories with a saved icon will start rendering it immediately on update.<\/li>\n<li>Bug fix: Long seller email addresses were overflowing the contact box in the single-listing sidebar. The <code>&lt;li&gt;<\/code> rows now apply <code>overflow: hidden; text-overflow: ellipsis; white-space: nowrap;<\/code> and pick up the missing right padding so the email truncates inside the bordered chip.<\/li>\n<\/ul>\n\n<h4>1.14.2<\/h4>\n\n<ul>\n<li>Bug fix: When creating a new listing in wp-admin, Leaflet threw <code>Invalid LatLng object: (undefined, undefined)<\/code> 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 (<code>assets\/js\/location.js<\/code>) on the post-edit screen. That script expects a different localize payload (<code>ucl_location_settings.latitude\/longitude<\/code>) than the metabox renderer provides (<code>ucl_map_settings.def_lat\/def_long<\/code>), so its values came up undefined. The redundant enqueue has been removed \u2014 the location field renderer enqueues the correct script (<code>assets\/fields\/location.js<\/code>) on its own when the section actually renders.<\/li>\n<li>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.<\/li>\n<li>Bug fix: <code>Control.Geocoder.js<\/code> now declares <code>ucl-leaflet-js<\/code> as a dependency so the geocoder loads after Leaflet on the post-edit screen.<\/li>\n<\/ul>\n\n<h4>1.14.1<\/h4>\n\n<ul>\n<li>Bug fix: The Listing Information metabox on the wp-admin post-edit screen (<code>post.php<\/code> \/ <code>post-new.php<\/code> for the <code>uclwp_listing<\/code> CPT) was rendering without Bootstrap CSS, the icon font, the iconpicker JS, the media uploader, or the map widget. The <code>load_admin_scripts()<\/code> 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 <code>post_type<\/code> is <code>uclwp_listing<\/code>. Bootstrap stays scoped to <code>.uclwp-bs-wrapper<\/code> so it does not affect the surrounding wp-admin UI.<\/li>\n<\/ul>\n\n<h4>1.14.0<\/h4>\n\n<p>Final WordPress.org Plugin Check pass \u2014 clears all reported errors for plugin-directory submission.<\/p>\n\n<ul>\n<li>Plugin name: Renamed to \"Ultimate Classified Listings \u2013 Classifieds, Directory &amp; Marketplace\" so the readme title matches the plugin header and the word \"Plugin\" (a restricted term) is no longer part of the displayed name.<\/li>\n<li>Tested up to: bumped to 7.0 to match the current WordPress release as required by .org.<\/li>\n<li>Escaping: Wrapped the dynamic CSS variable block emitted by <code>assets\/css\/styles.php<\/code> through <code>wp_strip_all_tags()<\/code> after each value is individually escaped via <code>esc_attr()<\/code>, with phpcs:ignore annotations explaining why the final concatenation is safe.<\/li>\n<li>Escaping: <code>paginate_links()<\/code> output in <code>render_pagination()<\/code> now passes through <code>wp_kses_post()<\/code> before echoing.<\/li>\n<li>i18n: Added <code>\/* translators: %s ... *\/<\/code> comments above two <code>printf( esc_html__( ... ), ... )<\/code> calls in the setup-wizard \"next steps\" panel that were missing them.<\/li>\n<li>Sanitization: Added <code>sanitize_text_field( wp_unslash( ... ) )<\/code> wrappers around <code>$_REQUEST['listing_id']<\/code> reads in <code>class-shortcodes.php delete_listing<\/code>, <code>class-email.php contact_seller<\/code>, <code>class-favorites.php ajax_toggle<\/code>, and <code>shortcodes\/dashboard\/edit.php<\/code>. The values are still validated through <code>uclwp_validate_listing_id()<\/code>.<\/li>\n<li>Sanitization: <code>$_SERVER['REQUEST_URI']<\/code> used to build the favorites login-redirect URL now passes through <code>esc_url_raw( wp_unslash( ... ) )<\/code> before <code>home_url()<\/code>.<\/li>\n<li>Sanitization: Recursive array inputs (<code>$_POST['uclwp_data']<\/code>, <code>$_REQUEST['sections']<\/code>, <code>$_REQUEST['fields']<\/code>) now have phpcs:ignore annotations documenting that each leaf is sanitized further down. The actual per-leaf sanitization was already in place.<\/li>\n<li>Sanitization: Login\/register password reads carry phpcs:ignore annotations explaining why passwords must not be passed through <code>sanitize_text_field()<\/code> (would strip control characters needed for hash comparison).<\/li>\n<li>File uploads in seller registration carry phpcs:ignore annotations pointing to the nonce check at the top of the handler.<\/li>\n<\/ul>\n\n<h4>1.13.0<\/h4>\n\n<p>Second WordPress.org Plugin Check pass \u2014 addresses the warnings flagged by the official PCP tool that the manual sanitization audit missed.<\/p>\n\n<ul>\n<li>Plugin Check: Added <code>defined( 'ABSPATH' ) || exit;<\/code> 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.<\/li>\n<li>Plugin Check: Replaced all <code>json_encode( $resp )<\/code> AJAX response calls with <code>wp_send_json( $resp )<\/code> so character encoding follows WP's UTF-8 conventions and the response is properly closed via <code>wp_die()<\/code>.<\/li>\n<li>Plugin Check: Replaced remaining non-AJAX <code>json_encode()<\/code> calls (Slick \/ image-grid data-attributes) with <code>wp_json_encode()<\/code>.<\/li>\n<li>Plugin Check: Added explicit <code>timeout<\/code> argument (10 s) to every <code>wp_remote_post()<\/code> call.<\/li>\n<li>Plugin Check: Added <code>rel=\"noopener noreferrer\"<\/code> to every <code>target=\"_blank\"<\/code> link in the admin settings help text, the post-update messages, and the setup-wizard \"next steps\" panel.<\/li>\n<li>Plugin Check: Removed <code>load_plugin_textdomain()<\/code> \u2014 WordPress 4.6+ loads translations automatically for plugins hosted on .org and the call is now flagged as discouraged.<\/li>\n<li>Plugin Check: Replaced bare <code>die();<\/code> \/ <code>die(0);<\/code> with <code>wp_die();<\/code> in AJAX handlers.<\/li>\n<li>Plugin Check: Added missing version arguments to every <code>wp_enqueue_style<\/code> \/ <code>wp_enqueue_script<\/code> call (used <code>UCLWP_VERSION<\/code> for bundled assets and <code>null<\/code> for third-party CDN scripts whose URL already carries a version key).<\/li>\n<li>Plugin Check: Renamed three filters that were missing the plugin prefix: <code>no_results_message<\/code> \u2192 <code>uclwp_no_results_message<\/code>, <code>seller_contact_email_message<\/code> \u2192 <code>uclwp_seller_contact_email_message<\/code>, <code>raw_uclwp_price<\/code> \u2192 <code>uclwp_raw_price<\/code>, <code>formatted_uclwp_price<\/code> \u2192 <code>uclwp_formatted_price<\/code>.<\/li>\n<li>Plugin Check: Removed nested <code>esc_attr( sanitize_text_field( \u2026 ) )<\/code> double-escape pattern in the search-form field renderer; the value is now sanitized once on input and the existing <code>esc_attr()<\/code> at output point handles HTML-attribute escaping.<\/li>\n<li>Plugin Check: Google-Maps script URL now built via <code>add_query_arg()<\/code> instead of string concatenation, and routed through HTTPS unconditionally.<\/li>\n<li>Plugin Check: Direct <code>$wpdb-&gt;update()<\/code> 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).<\/li>\n<\/ul>\n\n<h4>1.12.0<\/h4>\n\n<p>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.<\/p>\n\n<ul>\n<li>Sanitization: All <code>$_GET<\/code> \/ <code>$_POST<\/code> \/ <code>$_REQUEST<\/code> access across the plugin now runs through <code>wp_unslash()<\/code> before sanitization, matching WordPress core conventions and <code>WPCS<\/code> requirements.<\/li>\n<li>Escaping: Replaced <code>esc_attr_e()<\/code> \/ <code>esc_attr__()<\/code> with the correct <code>esc_html_e()<\/code> \/ <code>esc_html__()<\/code> everywhere the output is HTML body content (labels, table cells, headings, paragraph text). Attribute-context calls (<code>placeholder=\"\"<\/code>, <code>title=\"\"<\/code>, <code>value=\"\"<\/code>, <code>data-*=\"\"<\/code>) remain <code>esc_attr_e()<\/code>.<\/li>\n<li>Escaping: Replaced <code>esc_attr()<\/code> with <code>esc_html()<\/code> on user-controlled body text \u2014 listing titles, seller names, emails, phone numbers, category names, etc.<\/li>\n<li>Escaping: Textarea content now uses <code>esc_textarea()<\/code> instead of <code>esc_attr()<\/code>.<\/li>\n<li>Bugfix: <code>compare-box.php<\/code> printed nothing for compare table column headers due to <code>esc_attr( $label )<\/code> (which returns instead of echoing). Now properly <code>echo esc_html( $label )<\/code>.<\/li>\n<li>Hardening: Removed the second hardcoded reCAPTCHA fallback key in <code>templates\/sidebar\/default.php<\/code>. CAPTCHA now fails closed when not configured.<\/li>\n<li>Hardening: <code>gdpr_message<\/code> setting in the contact-seller form is now output via <code>wp_kses_post()<\/code> so admins can include safe HTML formatting (was <code>esc_attr()<\/code> which broke the HTML).<\/li>\n<li>Hardening: Search query builder (<code>uclwp_get_search_query<\/code>) sanitizes all dynamic <code>orderby<\/code>, <code>meta_key<\/code>, <code>tag<\/code>, and <code>range<\/code> inputs and validates against an allow-list before passing to <code>WP_Query<\/code>.<\/li>\n<li>Hardening: HTML emails (contact-seller) now <code>esc_html()<\/code> user-controlled fields (client name, email, phone, message, listing title) before HTML interpolation.<\/li>\n<li>Hardening: Compare AJAX listing IDs cast through <code>absint()<\/code>; meta_key segments through <code>sanitize_key()<\/code>.<\/li>\n<li>Hardening: <code>class-shortcodes::update_profile<\/code> casts and unslashes every <code>$_REQUEST<\/code> field.<\/li>\n<li>Hardening: Term-meta image\/icon callbacks (<code>save_category_image<\/code>, <code>updated_category_image<\/code>) now check <code>current_user_can('manage_categories')<\/code> and <code>wp_unslash()<\/code> the inputs (core verifies the nonce upstream on the term-edit screen).<\/li>\n<li>Hardening: Dashboard \"My Listings\" search query reads <code>$_GET['uclwp_status']<\/code> through an enum allow-list (<code>publish<\/code>, <code>pending<\/code>, <code>draft<\/code>, <code>future<\/code>, <code>any<\/code>).<\/li>\n<li>Compatibility: <code>selected()<\/code> and <code>selected( $a, $b )<\/code> used in place of inline <code>($a == $b) ? 'selected' : ''<\/code> ternaries where the markup allowed it.<\/li>\n<li>Compatibility: <code>(int) $cat-&gt;count<\/code> and <code>(int) count_user_posts(...)<\/code> casts in <code>page-sellers.php<\/code> and other admin lists in place of <code>esc_attr()<\/code> on integers.<\/li>\n<\/ul>\n\n<h4>1.11.0<\/h4>\n\n<ul>\n<li>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 \u2014 \"List\" (definition-list rows) and \"Inline\" (compact icon pills). Choose via Settings \u2192 Listings \u2192 Field Display on Single Listing Pages.<\/li>\n<li>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).<\/li>\n<li>Feature: Checkbox \/ features fields now render as accent-coloured chips with check icons, instead of plain bulleted text.<\/li>\n<li>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 <code>[uclwp_favorites]<\/code> shortcode. Toggle the feature in Settings \u2192 Listings \u2192 Favorites.<\/li>\n<li>Feature: Dark mode. Five options \u2014 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.<\/li>\n<li>Improvement: Field-display helpers now resolve safely when a value is empty so cards never render orphan labels.<\/li>\n<li>Improvement: <code>uclwp_listing_action_buttons<\/code> filter added so add-ons can inject extra card buttons without forking templates.<\/li>\n<li>Improvement: <code>uclwp_dashboard_allowed_pages<\/code> filter added.<\/li>\n<\/ul>\n\n<h4>1.10.0<\/h4>\n\n<ul>\n<li>Feature: Three new listing card styles. Style 2 \u2014 Minimal (flat, bordered, generous whitespace), Style 3 \u2014 Bold Overlay (full-bleed image with gradient and meta overlaid), Style 4 \u2014 Compact \/ Magazine (dense layout with prominent CTA). Each ships with both a grid and a list variant. Switch globally from Listings \u2192 Listing Card Style, or per-shortcode via <code>[uclwp_listings style=\"3\"]<\/code>.<\/li>\n<li>Feature: 14 new colour controls under Colors and CSS \u2014 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 <code>.uclwp-bs-wrapper<\/code> so themes and custom CSS can override them too.<\/li>\n<li>Feature: Card Corner Radius setting (0\u201364px).<\/li>\n<li>Feature: Card Image Aspect Ratio setting (Original \/ 4:3 \/ 16:9 \/ 1:1 \/ 3:4) \u2014 useful when seller-uploaded images vary in dimensions.<\/li>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.9.0<\/h4>\n\n<ul>\n<li>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.<\/li>\n<li>Feature: Auto-create essential pages with the correct shortcodes pre-inserted.<\/li>\n<li>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.<\/li>\n<li>Improvement: Admin classes now load only in <code>is_admin()<\/code> context to reduce frontend overhead.<\/li>\n<\/ul>\n\n<h4>1.8.0<\/h4>\n\n<p>This is a <strong>major security hardening release<\/strong>. All sites should update.<\/p>\n\n<ul>\n<li>Security: Added CSRF nonce verification to every public AJAX endpoint (<code>uclwp_search_listing<\/code>, <code>uclwp_seller_login<\/code>, <code>uclwp_seller_register<\/code>, <code>uclwp_create_listing_frontend<\/code>, <code>uclwp_delete_listing<\/code>, <code>uclwp_contact_seller<\/code>, <code>uclwp_compare_listings<\/code>).<\/li>\n<li>Security: Added CSRF nonce verification to admin AJAX endpoints (<code>uclwp_save_field_sections<\/code>, <code>uclwp_save_custom_fields<\/code>, <code>uclwp_reset_custom_fields<\/code>, <code>wcp_uclwp_save_settings<\/code>, <code>ucl_deny_seller<\/code>, <code>ucl_approve_seller<\/code>).<\/li>\n<li>Security: Pending-seller passwords are no longer stored in plaintext in <code>wp_options<\/code>. New registrations store only a salted hash; legacy entries are routed through the standard password-reset flow on approval.<\/li>\n<li>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.<\/li>\n<li>Security: Removed the hardcoded reCAPTCHA secret-key fallback. CAPTCHA verification now fails closed when not configured.<\/li>\n<li>Security: Replaced unvalidated <code>$_SERVER['REMOTE_ADDR']<\/code> use with <code>filter_var(... FILTER_VALIDATE_IP)<\/code>.<\/li>\n<li>Security: Listing IDs in delete and edit flows are now validated against the <code>uclwp_listing<\/code> post type and authorized via <code>current_user_can('edit_post'|'delete_post', $id)<\/code> instead of manual ownership checks.<\/li>\n<li>Security: Pending-seller <code>userindex<\/code> is cast to integer and validated against existing keys before use.<\/li>\n<li>Security: Permalink-settings save path now requires the core <code>update-permalink<\/code> nonce.<\/li>\n<li>Security: Added transient-based rate limiting on login, registration and contact-seller endpoints.<\/li>\n<li>Security: Asset URLs now include <code>UCLWP_VERSION<\/code> for cache busting after security updates.<\/li>\n<li>Bumped minimum WordPress to 6.0 and minimum PHP to 7.4.<\/li>\n<li>License header reconciled to GPLv2-or-later.<\/li>\n<\/ul>\n\n<h4>1.7<\/h4>\n\n<ul>\n<li>Bug Fixed: Security related issues<\/li>\n<\/ul>\n\n<h4>1.6<\/h4>\n\n<ul>\n<li>Bug Fixed: Security related issues<\/li>\n<\/ul>\n\n<h4>1.5<\/h4>\n\n<ul>\n<li>Bug Fixed: Security issues fixes, notified by Wordfence<\/li>\n<\/ul>\n\n<h4>1.4<\/h4>\n\n<ul>\n<li>Feature Added: Ability to choose category from the frontend<\/li>\n<li>Feature Added: Ability to assign tags from the frontend<\/li>\n<li>Bug Fixed: Empty categories are not displaying<\/li>\n<li>Bug Fixed: Security issues fixes, notified by WPScan Team<\/li>\n<\/ul>\n\n<h4>1.3<\/h4>\n\n<ul>\n<li>Bug Fixed: Security issues fixes, notified by WPScan Team<\/li>\n<\/ul>\n\n<h4>1.2<\/h4>\n\n<ul>\n<li>Bug Fixed: Iframes not saving from frontend<\/li>\n<\/ul>\n\n<h4>1.1<\/h4>\n\n<ul>\n<li>Feature Added: Iframes support in the shortcode field<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial Release<\/li>\n<\/ul>","raw_excerpt":"Lightweight classifieds &amp; directory plugin: frontend submission, AJAX search, maps with &quot;near me&quot;, SEO schema and sharing. Free, no API key.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/160100","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=160100"}],"author":[{"embeddable":true,"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/webcodingplace"}],"wp:attachment":[{"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=160100"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=160100"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=160100"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=160100"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=160100"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/kab.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=160100"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}