=== AuctionForge ===
Contributors: SmoothByte IT
Tags: auctions, bidding, auction house
Requires at least: 7.2
Stable tag: 3.7.4.6

== Changelog ==

= 3.7.4.6 =
* **Fixed: people using a keyboard or screen reader could not register at all.** The "I consent to the processing of personal data" checkbox was hidden in a way that removed it from keyboard navigation entirely, and registration will not proceed until it is ticked -- so anyone who could not use a mouse was locked out. The checkbox is now reachable by keyboard and announced by screen readers, with a visible focus outline.
* **Fixed: the Register button could be pressed repeatedly.** It stayed active while the registration was being processed, so an impatient bidder on a slow connection could send several attempts, trip the hourly limit and then be told they had registered when they had not. The button is now disabled while the request is in flight, and a request that stalls gives up after 45 seconds instead of hanging forever.
* **Fixed: a bidder blocked in error was left in a broken half-signed-in state** where the bid form stopped offering the login option and the links on the confirmation screen no longer worked.
* **Fixed: two JavaScript errors on the registration form** that could abort the sign-up part-way through, leaving no message on screen.
* **Fixed (single-step registration): a failed shipping-address update silently discarded the address.** The billing address was saved anyway, which locks the account against further edits, so the shipping address was lost permanently. It now stops and retries on the bidder's next visit instead.
* **Fixed (single-step registration): the form could become permanently unusable** if the country list failed to load -- the country box was required but empty, so the form refused to submit and said nothing. It now explains the list is unavailable and lets the bidder continue, adding their address later.
* **Fixed (single-step registration): phone number and US state are now required on the form.** Both are required to complete a BidSpirit profile, so leaving them blank meant the automatic profile completion failed over and over without telling anyone.
* **Fixed (single-step registration): the address is no longer sent when the account is first created.** It is applied after the bidder confirms their email, which is the whole point of the design -- sending it early defeated it.
* **New settings that previously had no on-screen control:** "Collect address during registration (single step)" and "Auction house name on invoices" (Settings -> Profile), and "Hot Lot view threshold" (Settings -> Features). The Popular badge could be switched on but its threshold was stuck at 5 views; the invoice house name had no field at all, so invoices that could not read the name from BidSpirit printed a blank heading -- it now falls back to your site title.
* **Fixed: a registrant's saved address could be read, changed or deleted by anyone.** While a bidder was between signing up and confirming their email, the stored address could be fetched, overwritten or removed by any visitor -- and an overwritten address is applied to their account automatically, which could have redirected where their winnings were shipped. All three now require proof that the request comes from the account holder. This only ever applied to single-step registration, which no site had switched on.
* **Fixed: the "filled in too fast" bot check had never worked.** It watched for form names the plugin does not use, so it never once triggered. One of the five bot-detection signals has effectively been switched off since it shipped, and is now live.

= 3.7.4.5 =
* **Changed: server-side registration is now on by default, and has a settings checkbox.** Introduced in 3.7.4.4 but only switchable from the command line, so in practice no house could turn it on. It now appears under Settings -> Profile as "Send registrations from this site, not the browser" and is enabled out of the box: a registration cannot reach BidSpirit without first passing the bot checks. Untick it if new registrations start failing; blocked attempts are always listed under Lots -> Honeypot so you can tell a genuine problem from bots being stopped.

= 3.7.4.4 =
* **Fixed: the registration honeypot was never actually on the form.** A hidden decoy field that only an automated script would fill has been part of the bot checks since 3.7.0, but no version ever put it on the registration form -- so the check could never fire, and never did. The field is now there, invisible to real visitors and skipped by keyboard navigation and screen readers.
* **Fixed: blocked signups caught in the browser were not being recorded.** When the browser spotted a bot it showed the decoy confirmation and reported the block to an address that does not exist, so those attempts vanished without trace. Every submission is now checked by the server, which records what it stops -- so the Honeypot screen shows the full picture rather than a partial one.
* **New: Honeypot screen** (Lots -> Honeypot, replacing "Blocked Signups"). Every stopped registration can be opened to show exactly what was submitted: the checks that failed, the visitor's IP, browser and referring page, their device fingerprint (graphics card, screen size, timezone, language and more) and every field they typed. You can search by email, IP, name or fingerprint, and the screen highlights repeat offenders -- the same device or address trying again under different names. Passwords are never recorded.
* **New (optional, off by default): server-side registration.** A new setting makes the website itself send the registration to BidSpirit, rather than the visitor's browser doing it after the checks have run. With it on, a registration cannot reach BidSpirit without passing the bot checks first. Leave it off and nothing changes. Houses should switch it on only after testing a real signup.

= 3.7.4.3 =
* **Changed: clearer prompt when a registrant reports a missing verification email.** The confirmation dialog now reminds them to check their junk/spam folder first ("Please be sure to check your junk/spam folders for the verification email."), and the button reads "No email received."

= 3.7.4.2 =
* **New: "Get ready to bid" progress checklist.** After registering, bidders now see a clear step-by-step checklist -- Account created, Verify your email, Add your details, Registration complete -- in place of the old plain notice, so they always know where they are and what comes next.
* **Changed: clearer per-auction wording.** For auctions that need approval or a card on file, the prompt now reads "Participate in this auction" (and, for a bidder who already has a card saved, "validate your card on file") instead of language that clashed with the account-level "approved" state and looked like a rejection. The old red error box is now a calm prompt.
* **Changed: no more premature "approved" pop-up.** The pop-up announcing account approval has been removed -- it used the word "approved" before a bidder could actually bid. Bidding access is handled by the per-auction prompt on the catalogue and lot pages.
* **New (optional, off by default): combined registration.** A new "Combined registration" setting lets a house collect the bidder's billing and shipping address on the registration form itself, so sign-up and "complete your details" become one step. The address is stored securely and applied automatically once the email is verified. No change unless a house turns it on.
* **Fixed: logging out no longer flashes a "no session" error** on the way to the login screen.
* **Fixed: the registration error message now appears next to the Register button** instead of at the top of the form, where it could be off-screen on a long form.
* **Changed: friendlier wording** when a bidder tries to edit their details while their account is still pending approval.

= 3.7.4.0 =
* **New: registration bot shield.** The previous name check only looked at names of eight or more characters, so the whole August 2026 signup wave ("Awbd Fplafk", "Cslh Flpohlrj", "Eczh Tunnbkdo") walked straight through it. Registrations are now scored on five signals together -- how the name is spelled, the shape of the email address, the browser's identity, how fast the form was filled in, and the hidden honeypot field. Blocked visitors see a normal confirmation message, so whoever is running the bots cannot tell a block from a success and cannot probe for the limits.
* **New: the name check is built from real bidder data instead of guesswork.** It compares each name against every three-letter sequence that occurs in around 18,700 verified bidders from across the fleet plus an English dictionary; a name is suspicious in proportion to how many of its sequences appear in no real name anywhere. Checked against known-good and known-bad accounts, it blocked 0 of 598 people who have actually bid, won or spent, 0 of 66 international control names (Welsh, Polish, Czech, Nordic, Indic and others), and caught 93% of both the August wave and the accounts previously flagged as bots by hand.
* **New: gmail dot-shuffling is recognised.** Gmail ignores full stops, so "r.i.n.v.il.lil.a@gmail.com" and "rinvillila@gmail.com" are the same inbox. The real inbox is now recorded next to whatever was typed, so repeat attempts through one mailbox are visible.
* **New: Blocked Signups screen** under Lots, listing what was stopped, why, and how much was stopped in the last 24 hours and 7 days.
* **Fixed: blocked registrations were never being recorded.** The log table was never created, so the code that writes to it returned early every single time and every block since the feature shipped was thrown away. It is now created automatically.
* **Fixed: registration could skip every check.** If the validation request failed for any reason, the site went ahead and registered the account anyway -- so anything that broke validation bypassed it entirely. That fallback is gone. Because pages are cached for longer than a security token stays valid, the form now quietly fetches a fresh token and retries once, so visitors on a cached page are unaffected.
* **Fixed: the hourly registration limit could be sidestepped.** The visitor's IP address was read from headers that anyone can set, so changing one value per request made every attempt look like a new visitor and the five-per-hour cap never applied. Those headers are now trusted only when the request genuinely comes through Cloudflare or a proxy on the same server.

= 3.7.3.4 =
* **New: bidders are shown exactly what their profile is missing, and cannot save an incomplete one.** After confirming their email, a new registrant lands on their profile with the required address fields still blank. The profile now lists the missing required fields in a panel at the top and outlines each empty field in red, and the Update button will not save until they are all filled in. The site-wide "please complete your registration" banner on the home, catalogue and lot pages now lists the same missing fields next to the Profile link. Required fields are First name, Last name, Email, Phone, Country, Address, City and Zip Code; State is additionally required when the bidder's own country is the United States (independent of the site's catalogue dialect). No schema or config-version change.
* **New: "not receiving the verification email?" report button.** While an account's email is still unconfirmed, the banner and the profile page now offer a third option next to "Resend email" and "Change it" -- a "Let us know" button that notifies the auction house's support contact that the verification email is not arriving, including the registrant's name, email and phone. Reports route by the site's US/UK dialect (Settings -> Appearance): US sites notify stephen@bidspirit.com, UK sites notify Alec@bidspirit.com. Rate-limited to three reports per visitor per hour. No schema or config-version change.

= 3.7.3.2 =
* **Fixed: lot image arrows were invisible on dark photos.** The previous/next arrows on the single lot page took their colour from the theme and fell back to black, so they vanished over black or dark backgrounds -- for example artefacts photographed on black. They are now a white chevron on a translucent dark disc, so they stand out on any image colour. Applies to both the Slider and Grid gallery layouts.
* **Changed: clearer name for the lot gallery layout setting.** The Appearance setting "Template for lot slider on" is now "Lot image gallery layout", with a description that explains the Slider and Grid options.

= 3.7.2.2 =
* **Fixed: turning on "Auto-refresh bids" put the start price back on shop items.** The 10-second refresh rebuilds each row's price from the live bid data, and with no bid to show it fell back to the start price -- so a wishlist row that correctly read "Purchase Price: $15" flipped to "Start price: $0" as soon as auto-refresh was switched on. The refresh now recognises shop items and keeps showing the purchase price; if it has no price to show it leaves the row alone rather than replacing a correct price with a wrong one.

= 3.7.2.1 =
* **Fixed: shop lots showed "Start price: $0".** A shop item is bought at a fixed price and has no start price -- BidSpirit reports it as 0 -- but the catalogue card and the wishlist still printed "Start price: $0", including on items that had already sold. Shop lots no longer show a start price anywhere, and the wishlist shows the purchase price instead. Applies to both the older "Auctions as shop" setting and the new BidSpirit Shop; ordinary auction lots are unchanged.
* **Changed: "Auto-refresh bids" is now an on/off toggle switch.** It was a plain checkbox whose label picked up the theme's link styling, so it read as a hyperlink rather than a setting. It is now a slider, and remains fully keyboard-operable.

= 3.7.2.0 =
* **New: BidSpirit Shop support.** BidSpirit's July 2026 API release added a Shop -- a fixed-price storefront the auction house manages alongside its auctions, bought outright rather than bid on. AuctionForge can now import it and sell from it. Switch it on under Settings -> Synchronization -> "Enable BidSpirit Shop" (off by default, so nothing changes on sites that do not use it); the house must also have Shop enabled on BidSpirit itself. Shop items are imported as ordinary lots under their own Shop page, so the existing catalog layout, CDN images, categories, site search, pagination and cache purging all apply unchanged, and purchases go through BidSpirit's shop/purchaseShopItem. Buyers need an account but do NOT need per-auction approval, so the shop is a lower-friction channel than an auction. Sold items stay listed and show their purchase price instead of a Buy button. The shop refreshes on the hourly sync, because BidSpirit sends no webhooks for shop stock. This is separate from the older "Auctions as shop" setting, which presents a real auction as a storefront -- both can be used on the same site and neither affects the other. No schema or config-version change.
* **Sold shop items are now marked as sold.** A shop item that has been bought stays listed but shows "Sold for" with the price it made, on both the shop page and the item page, instead of still showing a purchase price with no Buy button. A new setting, Settings -> Synchronization -> "Hide sold shop items", removes them from the listing entirely if you would rather they did not appear (off by default). Both apply only to the BidSpirit Shop; the older "Auctions as shop" presentation is unchanged.
* **Fixed: a "wait for the catalog to be published" notice appeared over the purchase confirmation on buy-now lots.** Shop and Buy-It-Now lots have no start price by design -- they are bought at a fixed price rather than bid on -- but the notice explaining an unpublished catalog keys on exactly that, so it fired on shop lots, and because completing a purchase reloads the lot it landed on top of the "thank you for your purchase" message. A cookie suppresses the notice for a week once dismissed, which is why it appeared only intermittently. The notice is now skipped on shop lots. Applies to both the older "Auctions as shop" setting and the new BidSpirit Shop; ordinary auction lots are unchanged.

= 3.7.1.24 =
* **Fixed: edits to pages, posts and Elementor templates did not clear the page cache.** Cache purging was wired only to auction changes, so editing a page -- in Elementor, in the block editor, or by changing a menu or a Customizer setting -- left the previous version cached and served to visitors until it expired on its own: up to 24 hours on an nginx FastCGI cache, plus whatever a CDN in front of it holds. It looked intermittent rather than broken because an unrelated auction sync would eventually purge the whole site anyway. Saving any page, post, lot, Elementor template, menu or Customizer setting now purges the cache, and all purges triggered by one request are combined into a single purge. Purging also now happens after the page has been sent back to the browser, so the editor's Update button no longer waits on the CDN. No schema or config-version change.

= 3.7.1.23 =
* **Fixed: lot videos showed "Error 153 - Video player configuration error" on iPhones and iPads.** Lazy-load plugins (WP Smush, LiteSpeed, WP Rocket) rewrote the lot gallery's YouTube `<iframe>` so it loaded from `about:blank` via `contentWindow.location.replace()`, which sends no `Referer` header on WebKit -- so every browser on iOS (Safari *and* Chrome) hit YouTube's `EMBEDDER_IDENTITY_MISSING_REFERRER` rejection while desktop and Android played the same video fine. The gallery iframe now carries `class="skip-lazy"` (honoured by all three lazy-loaders) plus native `loading="lazy"`, `referrerpolicy="strict-origin-when-cross-origin"` and YouTube's standard `allow` list, so it keeps lazy loading without losing the referrer. No schema or config-version change.

= 3.7.1.20 =
* **Fixed: "Auto commit mysql disabled" silently stopped sync jobs from completing.** On sites with that option ON, the lot importer set MySQL `autocommit=0` but never restored it, so the sync worker's "mark job completed" write was left in an uncommitted transaction and rolled back — every catalog/item sync job showed as failed even though the lot data saved. The importer now restores `autocommit=1` after it commits. Sites with the option off (default) are unaffected. No schema change.

= 3.7.1.19 =
* **Fixed: catalog category filter emitted invalid SQL and returned no lots.** When a listing used `bp_index` purely as a JOIN marker (e.g. the `bp_lots_by_category` widget / category-filtered catalog), `BidspiritQuery` built `CAST(bpMeta.\`itemIndex\` AS UNSIGNED)  0` with no comparison operator — a MariaDB syntax error that failed the whole query, so category-filtered listings showed nothing and the site error log filled with "error in your SQL syntax" (66k+ entries on the affected dev site since March). The itemIndex clause now emits a valid no-op when no comparison is supplied; real filtering (term/category) is unchanged. No schema or config-version change.

= 3.7.1.18 =
* **Added: admin control to require (or waive) bidder phone verification.** A new "Phone Verification" setting on the *Settings → Profile & Users* tab exposes the previously CLI-only `require_phone_verification` option as a dropdown — Automatic (required outside the UK), Always required, or Never required. Auction houses whose BidSpirit account is not provisioned for SMS phone verification can now switch it to "Never required" from wp-admin so bidders are no longer blocked at bid time. No schema or config-version change.

= 3.7.1.16 =
* **Fixed: auction pages returned HTTP 500 when an auction had no day schedule.** `page_auction.php` called `count()`/`foreach` on the `auction_days` term meta, which is `null` for auctions without a day schedule -- a fatal `count(null)` TypeError under PHP 8 that white-screened the auction page for visitors and search crawlers. `auction_days` is now normalised to an array before use. No schema change.

= 3.7.1.15 =
* **Cache now clears when a catalog is published or expanded.** Populating an auction's catalog imports its lots, which purged each lot page and its parent catalog page but NOT the template-rendered auction *listing* pages (home / archive / house pages). An auction that had just gained its first lots kept showing "Catalog available soon" on cached listings until the cache TTL expired. Importing a brand-new lot now also purges the listings -- coalesced into a single full purge per sync on shutdown (no per-lot thrash); plain lot updates are unaffected.

= 3.7.1.14 =
* **Cache now clears when an auction changes state (upcoming→live→ended).** Auction state transitions are time-based and arrive as `auction_state` term-meta writes (the `bp_auto_close` cron, SafetySync, the import pipeline) — which fire `updated_term_meta` but not `edited_<taxonomy>`, so the existing purge hooks never saw them and a finished auction kept showing "Enter Live Auction" / stayed in the upcoming list until the cache TTL expired. `CachePurge` now also hooks `added/updated_term_meta` for `auction_state` on auction terms and purges the listings on any state change.
* **nginx full-purge is now host-scoped.** On a shared server the FastCGI cache directory is used by every site, so a full purge (auction-level change) used to wipe unrelated sites' caches too. `NginxCacheSupport::purgeAll()` now parses each cache file's stored KEY and deletes only entries belonging to the current site's host.

= 3.7.1.13 =
* **nginx FastCGI/proxy cache purge on catalog & lot changes.** Sites fronted by nginx `fastcgi_cache` (micro-caching) now purge the same URLs already sent to Cloudflare and LiteSpeed, so catalog and item pages refresh on sync instead of serving a stale cached copy. Opt-in per site and completely inert otherwise, via a new `NginxCacheSupport` layer in `CachePurge`. Configure with `AF_NGINX_PURGE_URL` / `AF_NGINX_PURGE_METHOD` (the `ngx_cache_purge` module), or `AF_NGINX_CACHE_DIR` / `AF_NGINX_CACHE_LEVELS` / `AF_NGINX_CACHE_KEY` (direct cache-file deletion), or simply activate the rtCamp "Nginx Helper" plugin and AuctionForge delegates to it. Constants (wp-config.php) are preferred; matching `af_nginx_*` options also work.

= 3.7.1.12 =
* **Open-invoices banner now honours "Enable My Invoices tab".** The site-wide "This account has open invoices / Pay Now" banner rendered even on sites with the Invoices tab disabled, and its button led to a profile tab that does not exist. The banner (and its unpaid-bills lookups) is now skipped entirely when the tab is off: `BidspiritViews::bp_footer()` emits `window.afBillingTabEnabled` from the same `enable_billing_tab` setting that gates the tab, and `checkUnpaidBillsBanner()` returns immediately when it is false.
* **US auction houses resolve to the US "Catalog" dialect again (Auto mode).** Restores the address-signature detection lost in an earlier merge: US addresses end in "City, ST 12345" with no country name, so the state+ZIP tail was mistaken for an unknown country and resolved to "Catalogue". `Dialect::countryToDialect()` now recognises a US state code + 5-digit ZIP tail (and a bare trailing ZIP with a `$` currency as a secondary signal). Named countries — including Canada and Australia, whose `$` currencies and 4-digit/lettered postcodes do not match — still resolve to "Catalogue".
* **LiteSpeed purges during sync no longer stack admin notices.** Per-lot cache purges call LiteSpeed's `Purge::purge_url()` quietly instead of the public `litespeed_purge_url` action, which enqueued a dismissible "Purge url …" admin notice for every changed lot — hundreds after a full-auction sync. Manual purges elsewhere keep their confirmations; falls back to the action if the LiteSpeed class is unavailable.
* **Sync log no longer records one row per skipped auction.** The import worker logged "  Skipping auction N (excluded or not matched)" for every excluded auction on every run — the dominant source of `af_sync_log` growth on multi-house sites. It now logs a single "Skipped N/M auctions (excluded or not matched)" summary per run.
* **Housekeeping.** Removed stray `.bak` working files that had been shipping inside the plugin tree since 3.7.1.4.

= 3.7.1.11 =
* **"Auction is live" popup on lot pages.** When BidSpirit reports an auction as live, single-lot pages now show the "Auction is live — Enter live auction" popup (linking to the BidSpirit bidding console), exactly as auction catalogue pages already did. Previously a one-lot auction had no live indication anywhere on the site, because its catalogue URL redirects straight to the lot page and only the catalogue code path surfaced the live state.

= 3.7.1.2 =
* **Background sync worker spawn no longer times out under load ("Worker spawn HTTPS failed: cURL error 28").** `SyncQueue::spawnWorker()` triggered the worker via a blocking loopback request with a hard-coded 5s timeout, so whenever the site's own backend was slower than 5s the spawn aborted as a hard error and raised a sync alert even though the site was up. The timeout is now **15s by default** (overridable via the `AUCTIONFORGE_WORKER_SPAWN_TIMEOUT` constant or `auctionforge_worker_spawn_timeout` filter), the `127.0.0.1` loopback now uses the site's own scheme (avoiding an http→https redirect that forced the slower fallback), and a final non-blocking best-effort request now starts the worker when the backend is merely slow — logged as a warning rather than an error, with the hourly SafetySync cron as backstop. No schema/config change; healthy hosts behave exactly as before.

= 3.7.1.1 =
* **Active uptime monitoring (Kuma keyword health marker).** The keepalive mu-plugin now emits an invisible `<!-- af-health:ok -->` marker on every rendered front-end page (via `wp_head`/`wp_footer`). This lets Uptime Kuma verify the site is genuinely serving WordPress by pulling the homepage and asserting the marker is present — replacing the unreliable push-heartbeat model, which measured WP-Cron liveness rather than real availability and routinely false-flagged perfectly healthy sites as down. The marker lives in the mu-plugin, so it survives AuctionForge being deactivated or mid-update. Bundled keepalive bumped to 1.3.0 (auto-reinstalls on update); the legacy push heartbeat is retained this release for a safe transition.

= 3.7.0.43 =
* **Submission "Need help?" contact is now per-site.** The vehicle-submission help panel previously shipped a single hardcoded email/phone, so every site showed the same (wrong) contact regardless of tenant. It now reads the auction house's email and phone from the site's stored business details (SEO & AEO Optimizer LocalBusiness: `local_email` / `local_phone`), with `apply_filters('auctionforge_help_email')` / `apply_filters('auctionforge_help_phone')` overrides for other sources, and a neutral fallback that hides a row — or the whole block — when no value is set.

= 3.7.0.42 =
* **Submission form markup & content fixes (SEO).** The vehicle-submission templates rendered in the site footer on every page used `<h1>` headings ("Submit your vehicle for auction", "Detail received successfully"), producing multiple H1s in the page source. These are now `<h2>`, leaving a single H1 per page. Replaced the shipped Lorem-ipsum placeholder copy and the dummy "Need help?" contact details (`support@domain.com` / `+3265464325`) with real, production-appropriate text.

= 3.7.0.41 =
* **No more duplicate auction listings** — A brand-new auction could be created twice in WordPress when two syncs ran at the same moment (e.g. a bulk re-sync overlapping a catalogue webhook), each creating its own `auction_number` term before the other committed. The auction then appeared twice and its lots were split across the two copies. `Auction::save()` now serialises term creation per auction with an advisory lock and re-checks authoritatively against the database before inserting, so an existing auction is updated in place instead of duplicated. Complements the existing per-auction catalogue-sync lock that already prevents duplicate lot posts.

= 3.7.0.40 =
* **Lot countdown shows minutes** — The "begins to close" countdown on lot pages now ticks live and shows days/hours/minutes (and seconds in the final minute), so a lot less than an hour from closing no longer reads "0 days and 0 hours". Falls back safely to the previous display on stale-cached pages.

= 3.5.0.6 =
* **Bid Event Tracking** — All successful bids (regular, catalog, pre-sale buyout, post-sale purchase) are now recorded in the user tracking system with `event_type: bid`, capturing the bidder's email, IP, device fingerprint and full browser identity.
* **Self-Healing Database Schema** — Auto-detects and adds any missing tracking table columns on version upgrade.

= 3.5.0.5 =
* **Self-Healing Database Schema** — The plugin now auto-detects and adds any missing tracking table columns on version upgrade, ensuring sites running older database schemas seamlessly migrate without manual SQL intervention.

= 3.5.0.4 =
* **API Key Auto-Healing** — The WordPress plugin now seamlessly self-registers a fresh API Key when WPDeploy returns an `api_key_collision` quarantine blocker. This fully automates the security handover when deploying exact database clones to testing or proxy staging servers without showing ominous red warning cards to developers.

= 3.5.0.3 =
* **API Key Collision Defense** — Implemented an strict identity verification system to prevent cloned testing sites from polluting the central WPDeploy tracking network.
* **Network-Level Quarantine** — Central API dynamically extracts and strictly validates the incoming payload domain against the registered API Key domain, forcefully blocking mismatched traffic (HTTP 403).
* **Local Critical Alert Banner** — Added a persistent crimson warning banner in the WordPress admin panel that intercepts `api_key_collision` errors, directly instructing developers to update cloned keys.

= 3.5.0.2 =
* **Modal Trigger Hotfix** — Restructured internal UI rendering priority to gracefully deploy the inline Modal triggers inside custom IP log view without colliding with cached `z-index` stacks.

= 3.5.0.1 =
* **Hotfix:** Added automatic database schema updater for WPDeploy silent plugin upgrades to prevent column mismatch crashes on `wp_auctionforge_user_tracking`.

= 3.5.0.0 =
* **Full-Spectrum Device Fingerprinting** — Expanded the User Tracking system with 9 new forensic identifiers.
* WebRTC Local IP discovery exposes true internal network addresses even behind VPNs.
* Math Precision Fingerprint detects CPU architecture differences (Intel vs AMD vs Apple Silicon).
* Speech Synthesis voice enumeration and Media Device counting for unique hardware profiling.
* Battery API, Connection Profile, Color Gamut/HDR, and Accessibility preference tracking.
* Form Fill Timing measurement with automatic bot detection flag (<500ms = suspected bot).
* Async collection pipeline fires identifiers in background on page load for zero-latency capture.
* Fingerprint hash upgraded from fp2 to fp3 generation incorporating all new signals.
* Admin dashboard tooltip now shows dynamic identifier count per user with BOT? warning badge.

= 3.4.0.0 =
* **Sync Architecture Expansion** — Fully unified the WPDeploy remote console architecture matching identical features of native plugin.
* Standardized REST API endpoints including adding missing `/sync/auctions` route to managed instances.
* Optimized Sync Dashboard with queue controls, 5 active KPI gauges, and real-time streaming console.
* Resolved proxy JSON crash issues caused by HTML fallback responses on child instances lacking full endpoint routing.

= 3.2.9.2 =
* **Timed Auction Fixes** — Corrected a boolean check on the main upcoming/archive views that improperly hid the Timed Auction badge for catalog pages.

= 3.2.9.1 =
* **Timed Auction Enhancements** — Added a new WP-Admin setting "Change text to 'Auction will close in'" that updates the countdown prompt exclusively for timed/online auctions from "The auction will start in / Live bidding starts in" to "The auction will close in / Auction will close in" across single lot pages and wide upcoming auction shortcodes.

= 3.2.9.0 =
* **Billing & Invoices Polish** — Corrected front-end profile invoice ID tracking.
* Restructured data payloads feeding into `dompdf` generator to display accurate payment statuses rather than cached API defaults.
* Conditionally hid payment instructions on downloaded invoice files when the bill is fully paid.
* Changed "Invoice No" textual display to "Bidder No" on document output.

= 3.2.8.0 =
* **Billing & Invoices PDF Generation** — Implemented server-side generation using `dompdf` allowing users to directly download their exact HTML Bidspirit invoice locally via secure WordPress endpoints.
* Added native UI handling for `PAYMENT_IN_PROGRESS` invoice statuses correctly displaying "In Progress" with a "Resume" action button within the My Invoices interface.

= 3.2.7.0 =
* **Billing & Invoices Integration** — Added "My Invoices" tab to the user profile area.
* Logged-in users can now view their Paid and Unpaid invoices fetched dynamically from Bidspirit.
* New admin toggle under Bidspirit Options to enable or disable the billing tab.
* Unpaid bills include a direct "Pay Now" link.

= 3.2.4 =
* **Sync worker hang fix** — Sync workers were hanging indefinitely at the InsertLotsBS stage because third-party plugins (Smush Pro, SmartCrawl SEO, WPMU DEV) hooked into `wp_insert_post` and made blocking external HTTP calls for every lot. Added `WP_IMPORTING` constant and a `pre_http_request` filter to block all non-essential outbound HTTP during the batch post-insertion phase. Loopback and BidSpirit API calls are still allowed.
* **Stuck staging row recovery** — When workers were force-killed mid-sync, staging rows in `import_history` were left with `syncstart=1`, making them permanently invisible to subsequent sync runs. Workers now operate in an environment where this is mitigated by the faster completion times.

= 3.2.2 =
* **Full Sync discovery fix** — Full Sync now discovers auctions from BidSpirit API before syncing, fixing the issue where freshly deployed sites found 0 auctions.

= 3.2.0 =
* **Archive Sync** — New button on admin Sync Status page to re-sync all past (ARCHIVED/ENDED) auctions. Past auctions now appear in the dropdown.
* **Sync Tasks v3** — Admin task cards now use the v3 queue system. Old importData/importBidSpirit tasks replaced with queue-based Sync Active/Archived buttons.
* **How It Works guide** — Rewritten for v3 webhook-first architecture with job types, queue mechanics, cron setup, and troubleshooting.
* **Dual-write removed** — Legacy webhook handlers removed; all sync now goes through the v3 queue exclusively.
* **ITEMS_UPDATED fix** — Individual lot edits on BidSpirit now trigger real-time sync (auctionId extraction from nested payload).
* **ItemUpdateWorker fix** — Now processes staged data into WP posts + bp_meta_data via InsertLotsBS (was only staging).
* **Worker spawn fix** — HTTP loopback uses 127.0.0.1 with Host header to avoid nginx vhost routing issues.
* **API token** — Plugin expects `AUCTIONFORGE_API_TOKEN` constant in wp-config.php.
* DB version: 22 → 23

= 3.0.0 =

### New Feature: Webhook-First Sync Architecture (Sync v3)
- **Webhook-driven sync** — BidSpirit webhooks now enqueue jobs into a dedicated queue table (`af_sync_queue`) instead of relying solely on polling cron tasks. Data flows from webhook → queue → background worker → WordPress posts.
- **Job Queue System** — New `af_sync_queue` table with priority-based FIFO processing, automatic deduplication by auction_id + job_type, and atomic job claiming to prevent race conditions.
- **Background Worker** (`syncWorker.php`) — Async CLI process spawned by webhooks or the safety cron. Claims and processes queued jobs with a configurable timeout (default 240s). Handles catalog_sync, item_update, full_sync, day_ended, and archive_sync job types.
- **Hourly Safety Net** (`syncSafety.php`) — Replaces the old per-minute importData and hourly importBidSpirit cron tasks with a single hourly cron that: resets stuck jobs, spawns workers for pending jobs, runs a 24-hour health check (auto-enqueues full sync if no webhooks received), and purges old data.
- **Real-time Live Console** — New dark-themed terminal console on the Sync Status admin page. Polls `af_sync_log` every 1.5s showing timestamped, color-coded log entries from webhooks, workers, queue operations, and the safety cron. Supports pause, clear, and auto-scroll.
- **Queue Status Dashboard** — New card on Sync Status page showing pending/processing/completed/failed/skipped counts, recent jobs table, worker status badge, and action buttons (Full Sync, Sync Selected Auction, Spawn Worker).
- **Dual-write mode** — Webhooks enqueue jobs AND still run the legacy handlers during transition. Once verified, legacy handlers can be removed cleanly.
- **WP-Cron safety hook** (`auctionforge_sync_safety`) — Always-active hourly WP-Cron event that resets stuck jobs and spawns workers, independent of the external cron system.
- **Cron installer v3** (`setup.sh`) — Updated task definitions: removed `importData` (every minute) and `importBidSpirit` (hourly), added `syncSafety` (hourly at :10). New `migrate` command for interactive v2→v3 crontab transition.
- **cURL timeout** — BidSpiritRequest now has a 30-second cURL timeout (was unlimited) to prevent hung connections blocking the sync pipeline.

### New Files:
- `inc/sync/SyncQueue.php` — Queue model with enqueue, claim, complete, fail, skip, reset, spawn, purge
- `inc/sync/SyncLogger.php` — Real-time logging with level filtering and incremental polling
- `inc/sync/CatalogSyncWorker.php` — Processes catalog/full/day-ended sync jobs
- `inc/sync/ItemUpdateWorker.php` — Processes individual item update jobs
- `crontasks/syncWorker.php` — CLI background worker process
- `crontasks/syncSafety.php` — CLI hourly safety net process

### New DB Tables:
- `af_sync_queue` — Job queue (status, priority, source, attempts, worker PID, timing)
- `af_sync_log` — Real-time log entries (context, level, auction_id, job_id)

### Technical Details:
- DB version bumped from 20 to 22
- 7 new constants: AUCTIONFORGE_SYNC_WORKER_TIMEOUT, AUCTIONFORGE_SYNC_STALE_JOB, AUCTIONFORGE_SYNC_HEALTH_CHECK, AUCTIONFORGE_SYNC_LOG_RETENTION, AUCTIONFORGE_SYNC_QUEUE_RETENTION, AUCTIONFORGE_CURL_TIMEOUT, AUCTIONFORGE_PHP_BINARY
- 3 new AJAX handlers: auctionforge_sync_log_poll, auctionforge_sync_queue_status, auctionforge_sync_enqueue
- Net cron change: 5 entries → 4 entries, ~62 PHP executions/hour → 1

= 2.7.1.2 =

### Fixes:
- Added missing `vatAndCommission()` function in `loadBid()` — the sales tax display on single lot pages was never being rendered because the code to call `loadTemplate` for `vat-and-commission-template` was missing from `app.js`. Also added missing `setAuctionItemInfo()` call.

= 2.7.1.1 =

### Fixes:
- 2 decimal places now only appear in the bid modal (Your max bid, Total Price) via `showDecimals` flag. Start price, increments list, and other prices show whole numbers as before.

= 2.7.1.0 =

### Fixes:
- Fixed Buyer's Premium value not showing on single lot pages — added missing `auction-commission-field` population in `auctionInfoInit()` from upstream reference.

= 2.7.0.9 =

### Fixes:
- Fixed fatal error during plugin install/upgrade: `class-stats-database.php` require failure. Added `file_exists` guard in `AF_Stats_Init::activate()` and plugin-specific guard in `upgrader_process_complete` hook to prevent running upgrade logic for unrelated plugin updates.

= 2.7.0.8 =

### Fixes:
- Synced `formattedPrice()` across both `app.js` and `app.min.js` — added second currency support, better null/NaN handling, and always 2 decimal places on all prices. Fixes sites where minification plugins may load `app.min.js` instead of `app.js`.

= 2.7.0.7 =

### Fixes:
- All prices now display with 2 decimal places (e.g. $10.00 instead of $10) via `formattedPrice()` using `toLocaleString` with `minimumFractionDigits: 2`.

= 2.7.0.6 =

### Fixes:
- Added missing `estimatedPrice`, `content`, and `isShop` template variables to widget and shortcode `lot-content-template` calls, preventing potential ReferenceErrors on catalogue pages with ended auctions or shop items.

= 2.7.0.5 =

### Fixes:
- Fixed Stripe payment form not visible in Add Payment Method modal — `#stripePaymentElement` had `hidden` class that was never removed. Now unhides form on Stripe `ready` event and re-hides on modal close.

= 2.7.0.4 =

### Fixes:
- Fixed `bidspiritApi.getAuctionSettingsData is not a function` error in getFullPriceInfo — use local auctionSettingsData variable instead of non-existent API method.

= 2.7.0.3 =

### Fixes:
- Fixed `fullPriceInfo is not defined` ReferenceError that prevented bid confirmation modal from opening on single lot pages and catalogue pages.
- Added missing `getFullPriceInfo()`, `getCommissonInfoForPrice()`, and `roundToCents()` functions ported from upstream.
- All three bid submission paths (submitBid, submitBidCatalog, submitBidCatalog3) now correctly pass fullPriceInfo to the modal-bid-template.

= 2.7.0.0 =

### New Features: Advanced Lot Analytics
- **Conversion Tracking** — View-to-bid ratio table showing lot views, catalogue/external clicks, favorites, shares, bid status, and sold price with sell-through rate summary cards
- **Peak Viewing Times** — Hourly bar chart showing when lots get the most views (last 30 days) with peak hour identification
- **Pre-Sale vs Live Comparison** — Per-auction breakdown of views before auction day vs during/after, with pre-sale percentage
- **Category Performance** — Analytics grouped by lot category: views, lots, favorites, sold count, average views per lot
- **Share Analytics** — Track shares by channel (Facebook, Twitter, WhatsApp, Email, Pinterest, Copy Link) with per-channel badges and most-shared lots table
- **Search → View Funnel** — Correlates internal search queries with subsequent lot page views to show which searches lead to browsing
- **Click-Through Tracking** — Distinguishes catalogue clicks (user came from auction catalogue) vs external clicks (direct/search/referral)
- **CSV Export** — One-click export of all lot analytics data, filterable by catalogue
- **Hot Lot Badge** — Automatic "🔥 Popular" badge on catalogue page thumbnails for lots exceeding view threshold (default: 5 views)
- All new panels respect the catalogue filter dropdown
- New DB table: af_lot_shares (per-channel share events)
- New columns: catalogue_clicks, external_clicks, share_count on af_lot_analytics
- DB version bumped to 20

= 2.6.3.0 =

### Fixes:
- View counter now shows on lot pages even with 0 views
- Fixed backfill and trending queries failing on URLs with trailing slashes

= 2.6.2.0 =

### Fixes:
- Fixed 404 errors on auction/lot pages after DB version update by flushing rewrite rules automatically

= 2.6.1.0 =

### Fixes:
- Renamed shortcodes to use bp_ prefix for consistency: [bp_popular_lots], [bp_most_favorited], [bp_trending_lots]

= 2.6.0.0 =

### New Feature: Lot Analytics
- Added comprehensive lot view and favorite tracking system
- Two new settings in AuctionForge > Settings (both disabled by default):
  - **Enable Lot Analytics** — master toggle for the entire tracking system
  - **Show View Counter on Lots** — display a public view count on each lot page
- New admin dashboard tab: **Lot Analytics** (under Site Statistics) with:
  - Auction Performance comparison table
  - Most Viewed Lots (all time)
  - Most Favorited Lots
  - Trending Lots (last 7 days)
  - Backfill from existing pageview data button
- Local favorites tracking mirrors BidSpirit favorites for analytics
- Three new shortcodes for displaying popular lots:
  -  — most viewed lots grid
  -  — most favorited lots grid
  -  — trending lots (configurable time window)
- All shortcodes support limit, columns, auction_id, and period filtering
- New DB tables: af_lot_analytics, af_lot_favorites (DB version 19)

= 2.5.9.0 =

### Improvements:
- Redesigned Recently Viewed items as square card grid (rows/columns) instead of horizontal list.
- Changed lot thumbnail to contain (show full image) instead of cover (crop to fill).

= 2.5.8.0 =

### Fixes:
- Fixed Recently Viewed cloned image thumbnails using non-existent resize prefix. Now uses raw image filename for reliable CDN loading.

= 2.5.7.0 =

### Fixes:
- Fixed Recently Viewed thumbnail URLs — now builds correct BidSpirit CDN URLs for both cloned (Fastly) and non-cloned (Cloudinary) images instead of broken relative paths.

= 2.5.6.0 =

### Fixes:
- Fixed placeholder image fallback path still referencing old uco-auctions directory.
- Added background color for empty thumbnail containers in Recently Viewed.

= 2.5.5.0 =

### Improvements:
- Removed PAST/Upcoming badges from Recently Viewed items display.
- Added lot thumbnail images to Recently Viewed items with proper card layout.

= 2.5.4.0 =

### Fixes:
- Fixed item variable not being passed to auctionIsLive-template (was lost during previous file edits).

= 2.5.3.0 =

### Fixes:
- Fixed `item is not defined` in `displayBidFormData` — removed stray `item: item` from house-info-template call where item variable does not exist in scope.

= 2.5.2.0 =

### Fixes:
- Fixed `item is not defined` JavaScript error in `auctionIsLive-template` on single lot pages.
- Fixed statistics table auto-creation running on frontend requests (restricted to admin only).

= 2.5.1.0 =

### Fixes:
- Fixed `hideleadingBidSection is not defined` JavaScript error on single lot pages.
- Fixed statistics database tables not auto-creating on plugin update (only activated on fresh install).
- Added self-healing table creation on `plugins_loaded` for sites that update without re-activating.
- Cleaned up duplicate `AF_Stats_Init::activate()` calls in DB update function.
- Bumped DB version to 18.

= 2.5.0.0 =

### New Features:
- **Site Statistics System** — Full website analytics built into the plugin admin (Lots → Site Statistics).
  - Dashboard with KPI cards: visitors, sessions, pageviews, bounce rate, avg duration, pages per session.
  - Period comparison: current vs previous period with percentage change indicators.
  - Traffic over time line chart (pageviews, visitors, sessions).
  - Traffic channels doughnut chart (organic search, direct, referral, social, email, paid).
  - Hourly distribution bar chart.
  - Device type pie chart (desktop, mobile, tablet).
  - Top Pages tab with views, unique visitors, avg time on page, search, pagination, CSV export.
  - Referrers tab with traffic channel badges, sessions, bounce rate, avg duration, CSV export.
  - Countries tab with horizontal bar chart and visitor/pageview counts.
  - Technology tab with browser, OS, device breakdown and screen resolutions.
  - Searches tab tracking internal WordPress and auction search queries.
  - 404 Errors tab tracking broken URLs with hit counts and referrers.
  - Real-Time visitors view (last 5 minutes, auto-refreshes every 10 seconds).
  - Settings tab: enable/disable tracking, exclude IPs and roles, IP anonymization (GDPR), data retention.
  - Date range picker with presets (7, 14, 30, 90, 365 days) and custom range.
  - Lightweight frontend tracker (~3KB JS) with session management, heartbeat, and beacon API.
  - GeoIP country/city lookup via ip-api.com with 24-hour cache.
  - Bot detection (30+ crawler patterns filtered).
  - UTM parameter tracking (source, medium, campaign, term, content).
  - Traffic channel auto-classification.
  - Outbound link click tracking.
  - Time-on-page tracking via 30-second heartbeat + page unload beacon.
  - Daily cron for automatic data retention purge.
- **Recently Viewed Items** — New profile page tab showing the last 20 viewed lots per user.
  - Admin setting to enable/disable (Lots → Settings → Enable user viewing history).
  - Tracks lot views with timestamp, stores in custom DB table.
  - Clear history button for users.
  - Disabled by default.

### Fixes:
- Fixed `settings is not defined` JavaScript error on auction catalog and single lot pages.
- Fixed `postAuctionSaleOpened is not defined` JavaScript error on auction pages.
- Fixed missing `vendor.min.css` and `password-validation.min.css` (404 errors).
- Added missing template variables to all `loadTemplate` calls for `lot-content-template`, `leadingBid-template`.

= 2.4.4.0 =

### Fixes:
- Fixed the display of purchase prices on shop pages for unsold items. Items with a buyout price now correctly show the "Purchase Price" in both ended and active auction states.

= 2.3.5 =

### New Features:
- Added the ability to create a menu item that opens a modal login window. (`[href="#bp-profile"]`)
- Prices are now displayed with a secondary currency.
- Added an option to display prices including taxes.

### Improvements:
- Login and registration pages no longer leave users on the default welcome page after authentication.
- Updated German localization.
- Added Dutch localization.
- Optimized the synchronization process for auction houses with a large number of auctions.
- Updated the user manual. Added a section with available shortcodes.

### Fixes:
- Fixed a synchronization issue when default and secondary language settings were incorrect in the plugin.
- Fixed broken content in the *Make an inquiry about this item* link when special characters were present in the lot title.
- Fixed display of current bids for lots depending on the auction house settings.
- Fixed display of current bids for lots that are loaded when scrolling the page, if the "Enable pagination" option is disabled.
- Fixed handling of lots that were removed from the auction.
- Fixed bid display on the catalog page when the *enablePrebidsUnderStartPrice* option is enabled.