# AuctionForge Changelog

All notable changes to the AuctionForge plugin are documented in this file.

---

## [3.2.9.2] — 2026-03-30

### Timed Auction Archive Fix

#### Fixes
- **Timed Auction Badge Visibility** — Fixed a strict type-matching bug in the main `auction_list.php` core file that incorrectly hid the "Timed Auction" badge overlay from valid timed/online auctions on the main upcoming/archive views. The badge now displays properly across all manual and Bidspirit-synced objects when toggled on.

#### Files Changed
- **Modified:** `templates/main/auction_list.php`

---

## [3.2.9.1] — 2026-03-30

### Timed Auction Display Enhancements

#### Features
- **Timed Auction Countdown Text Toggle** — Added a WP-Admin setting "Change text to 'Auction will close in'" that conditionally changes the countdown label for timed/online auctions on single lot pages.
- **Shortcode Text Toggle Support** — Extended the new countdown text logic to the `bp_upcoming_wide` shortcodes so "Live bidding starts in:" successfully transitions to "Auction will close in:" for timed auctions.

#### Files Changed
- **Modified:** `inc/controllers/Settings.php`, `templates/single-lot.php`, `templates/shortcodes/upcoming-auction-wide.php`, `templates/shortcodes/upcoming-auction-wide-manual.php`

---

## [3.2.9.0] — 2026-03-29

### Billing & Invoices Polish

#### Fixes
- **Profile Invoice ID Tracking** — Prevented internal auction codes (e.g. `002` or `ai4`) from breaking into the clean hyphenated titles inside the `profile-templates.php` view.
- **Dynamic Payment Statuses** — Replaced Bidspirit's default/cached `NOT PAID` HTML payloads by wiring the `dompdf` generator to directly respect the current `$b.status` string from the API (resolving instances where a paid invoice still printed "NOT PAID" atop the document).
- **Payment Instructions Conditional** — Automatically hide the "Payment Instructions" block from the PDF footer when an invoice is fully paid.
- **Improved Nomenclature** — Changed "Invoice No:" to "Bidder No:" on document output to prevent confusion with static invoice ledger IDs.

---

## [3.2.6.0] — 2026-03-28

### Multi-Day Auction Lot Filtering Fix

#### Fixes
- **Day 2+ showing lots from Day 1 in multi-day auctions** — The `itemIndex` column in `bp_meta_data` is `varchar(55)`, but the day-range filter in `BidspiritQuery::bp_query_where()` was comparing it as a string. With string comparison, lot numbers like `'11'`, `'101'`–`'145'` incorrectly fell between `'1001'` and `'1450'`, causing Day 1 lots to leak into Day 2 results. Fixed by using `CAST(bpMeta.itemIndex AS UNSIGNED)` for numeric comparison and `intval()` on the PHP-side boundary values. Letter lots (e.g. `14A`, `1025A`) are handled correctly — `CAST` strips the suffix, keeping them grouped with their base lot number.

#### Files Changed
- **Modified:** `inc/controllers/BidspiritQuery.php`

---

## [3.2.5.9] — 2026-03-27

### Catalog Page Fix

#### Fixes
- **Duplicate preview hours on catalog pages** — The `displayHours` field was rendered twice: once via the `bp_after_description_auction` hook in `BidspiritViews.php` and once inline in the auction template. Removed the hook-based rendering to eliminate the duplication.

#### Files Changed
- **Modified:** `inc/controllers/BidspiritViews.php`, `templates/main/auction.php`

---

## [3.2.5] — 2026-03-19

### Sync Reliability & Stuck Lot Prevention

#### Fixes
- **CATALOG_PUBLISHED webhook now triggers catalog sync** — Previously only sent push notifications without syncing lots. When the earlier AUCTION_CATALOG_UPDATED fired before the catalog was ready on BidSpirit, lots were never imported. CATALOG_PUBLISHED now calls `auctionCatalogUpdated()` to trigger a full catalog sync when the catalog is actually available.
- **0-lot API results now fail instead of silently succeeding** — When BidSpirit API returns 0 lots (catalog not yet ready), the sync job now fails with an explicit error message, allowing the next webhook to create a fresh retry job.
- **Expanded CDN allowlist in pre_http_request filter** — The HTTP filter during bulk post insertion now allows all `*.bidspirit.com` subdomains and BidSpirit image CDNs (`fastly.net`, `cloudfront.net`). Previously only `bidspirit.com` and `www.bidspirit.com` were allowed, blocking image downloads during sync and causing worker hangs.
- **InsertLotsBS timeout guard** — Stage 5 (InsertLotsBS processing loop) now has a 180-second timeout and logs progress every 2 batches. Prevents indefinite hangs that previously required manual force-kill.
- **fixResult() no longer marks lots as done when postId=0** — When `lotProcessing()` fails to create a WP post, the staging row now keeps `syncFinished=0` so the lot is retried on the next sync pass, instead of being permanently marked as complete with no actual post.
- **lotProcessing() logs error on missing auction term** — Previously returned 0 silently when `Auction::findAuctionById()` failed. Now logs an explicit error that feeds into `fixResult()` to keep the lot retryable.
- **Hourly orphan detection in safety cron** — New step in `syncSafety.php` detects lot posts that exist in `bp_meta_data` but are missing their auction taxonomy assignment (caused by workers killed mid-batch) and auto-repairs them with `wp_set_object_terms()`.

#### Files Changed
- **Modified:** `inc/controllers/AuctionsAPI.php`, `inc/sync/CatalogSyncWorker.php`, `inc/abs/InsertLotsAbstract.php`, `inc/import/InsertLotsBS.php`, `crontasks/syncSafety.php`

---

## [3.2.4] — 2026-03-18

### Sync Worker Hang Fix

#### Fixes
- **Sync workers hanging at InsertLotsBS stage** — Third-party plugins (Smush Pro, SmartCrawl SEO, WPMU DEV Updates) hook into `wp_insert_post()` and make blocking outbound HTTPS calls for every lot processed. With 489+ lots per auction, this caused workers to hang indefinitely on socket reads to WPMU DEV servers (`32.193.94.25`, `32.193.93.106`). Added `WP_IMPORTING` constant at the REST worker entry point and a `pre_http_request` filter in `CatalogSyncWorker` that blocks all non-essential external HTTP during the batch post-insertion phase. Loopback (`127.0.0.1`, `localhost`) and BidSpirit API calls remain allowed.
- **Stuck staging rows after force-kill** — When workers were force-killed mid-sync, staging rows in `import_history` were left with `syncstart=1` and never unblocked. Subsequent sync runs could not see these rows (filtered out by `Lot::getRawLots()`), causing the InsertLotsBS loop to exit immediately with 0 posts processed. Combined with job deduplication (`SyncQueue::enqueue()` skips if a `processing` job exists), this created a deadlock where no new sync could proceed.

#### Files Changed
- **Modified:** `auctionforge.php`, `inc/sync/CatalogSyncWorker.php`

---

## [3.2.2] — 2026-03-17

### Full Sync Auction Discovery Fix

#### Fixes
- **Full Sync finds no auctions on fresh sites** — The v3 `full_sync` action only queried the WordPress database for existing auction terms, but on a freshly deployed site the database has zero auctions. Added `AuctionList::init()` discovery step before querying the DB, so Full Sync now fetches all auctions from the BidSpirit API and creates taxonomy terms first, then enqueues catalog sync jobs for each.

#### Files Changed
- **Modified:** `auctionforge.php`

---

## [3.2.0] — 2026-03-14

### Webhook Pipeline Fixes, Archive Sync & Admin UI Overhaul

#### New Features
- **Archive Sync** — New "Archive Sync" button on Sync Status admin page. Enqueues catalog sync jobs for all ARCHIVED/ENDED auctions. Past auctions now appear in the auction dropdown with an optgroup separator and state label.
- **Sync Tasks v3** — Replaced old v2 task cards (importData, importBidSpirit, lotBidSpiritOld) with v3 queue-based tasks. "Sync Active Auctions" and "Sync Archived Auctions" now enqueue jobs via the queue system. Maintenance tasks (Clean Draft/Trash Lots, Clean Orphaned Meta) remain as direct-execution tasks.
- **How It Works guide rewritten** — Complete rewrite of the admin guide for v3 architecture. Documents webhook-first flow, all 5 job types, queue system mechanics, cron setup (external + WP-Cron), configuration constants, and troubleshooting.

#### Fixes
- **Dual-write removed** — Removed legacy handlers from `auctionCatalogUpdated()`, `auctionDayEnded()`, and `itemsUpdated()` webhook methods. The old handlers were racing with the v3 worker, creating orphaned staging rows in `import_history` that were never processed. All three methods now exclusively use the v3 enqueue → spawn path.
- **ITEMS_UPDATED webhook** — Fixed `itemsUpdated()` failing to enqueue jobs. The `ITEMS_UPDATED` payload has `auctionId` nested in `itemsByLanguage.{lang}[0].item.auctionId`, not at the top level. Added extraction logic to resolve it.
- **ItemUpdateWorker incomplete** — `ItemUpdateWorker::process()` was only calling `Lot::bulkSave()` (staging table) without running `InsertLotsBS` to process staged data into WP posts and `bp_meta_data`. Added the InsertLotsBS processing step with autocommit restore. Item updates now flow: stage → InsertLotsBS → WP post + meta → cache clear.
- **Worker spawn reliability** — Changed `SyncQueue::spawnWorker()` from non-blocking HTTPS to blocking HTTP loopback (`http://127.0.0.1` with explicit `Host` header, 2s timeout). Fixes nginx 403/404 errors when HTTPS loopback hits the default vhost instead of the site's vhost.
- **API token constant** — Plugin expects `AUCTIONFORGE_API_TOKEN` in `wp-config.php` (renamed from `UCO_API_TOKEN` during the plugin rename). Documented in readme.

#### Database
- DB version: 22 → 23

#### Files Changed
- **Modified:** `inc/controllers/AuctionsAPI.php`, `inc/sync/ItemUpdateWorker.php`, `inc/sync/SyncQueue.php`, `auctionforge.php`, `admin/view/sync-status.php`, `readme.txt`

---

## [3.0.0] — 2025-03-14

### New Feature: Webhook-First Sync Architecture (Sync v3)

**Overview:** Complete redesign of the BidSpirit data synchronisation pipeline. Replaces the old two-phase polling system (importBidSpirit every hour + importData every minute) with a webhook-driven job queue that processes data in near real-time.

#### Architecture
- **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: webhook → queue → background worker → WordPress posts.
- **Dual-write mode** — During transition, webhooks enqueue jobs AND still run the legacy handlers. Once verified, legacy handlers can be removed cleanly.
- **Job deduplication** — Prevents duplicate work by checking for existing pending/processing jobs with the same auction_id + job_type.

#### New Components
- **Job Queue System** (`inc/sync/SyncQueue.php`) — Priority-based FIFO queue with atomic job claiming, status tracking (pending/processing/completed/failed/skipped), worker PID tracking, and automatic stuck job recovery.
- **Sync Logger** (`inc/sync/SyncLogger.php`) — Real-time log table for admin console polling with level filtering (info/success/warning/error) and incremental cursor-based retrieval.
- **Catalog Sync Worker** (`inc/sync/CatalogSyncWorker.php`) — Processes catalog_sync, full_sync, and day_ended jobs by reusing existing `LotList::singleRun()` logic.
- **Item Update Worker** (`inc/sync/ItemUpdateWorker.php`) — Processes individual item_update jobs with the same logic as the webhook `itemsUpdated` handler.
- **Background Worker** (`crontasks/syncWorker.php`) — Async CLI process spawned by webhooks via `exec()` or by the safety cron. Claims and processes queued jobs with a configurable timeout (default 240s).
- **Safety Net** (`crontasks/syncSafety.php`) — Hourly cron that resets stuck jobs (>10min), spawns workers for pending jobs, runs a 24-hour health check (auto-enqueues full sync if no webhooks received), and purges old data.

#### Admin UI
- **Queue Status Dashboard** — New card on Sync Status page showing pending/processing/completed/failed/skipped counts, recent jobs table with real-time refresh, worker status badge, and action buttons:
  - **Full Sync** — Enqueues catalog sync for all active auctions
  - **Sync Selected** — Enqueue a single auction from dropdown
  - **Spawn Worker** — Manually trigger a background worker process
- **Live Console** — Dark-themed terminal console on the Sync Status 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.

#### Cron Changes
- **Removed** `importData` (ran every minute — ~1,440 PHP executions/day)
- **Removed** `importBidSpirit` (ran hourly — ~24 PHP executions/day)
- **Added** `syncSafety` (runs hourly at :10 — 24 PHP executions/day)
- **Net effect:** ~62 PHP executions/hour → 1 PHP execution/hour
- **WP-Cron safety hook** (`auctionforge_sync_safety`) — Always-active hourly event, independent of external cron
- **setup.sh v3** — Updated task definitions, new `migrate` command for interactive v2→v3 transition

#### Database
- New table: `af_sync_queue` — Job queue with status, priority, source, attempts, worker PID, timing columns
- New table: `af_sync_log` — Real-time log entries with context, level, auction_id, job_id
- DB version: 20 → 22

#### Constants Added
| Constant | Default | Description |
|---|---|---|
| `AUCTIONFORGE_SYNC_WORKER_TIMEOUT` | 240 | Worker process timeout in seconds |
| `AUCTIONFORGE_SYNC_STALE_JOB` | 600 | Seconds before a processing job is considered stuck |
| `AUCTIONFORGE_SYNC_HEALTH_CHECK` | 86400 | Seconds without webhook before triggering full sync |
| `AUCTIONFORGE_SYNC_LOG_RETENTION` | 7 | Days to keep log entries |
| `AUCTIONFORGE_SYNC_QUEUE_RETENTION` | 30 | Days to keep completed queue jobs |
| `AUCTIONFORGE_CURL_TIMEOUT` | 30 | BidSpirit API cURL timeout in seconds |
| `AUCTIONFORGE_PHP_BINARY` | /www/server/php/83/bin/php | PHP CLI binary path for spawning workers |

#### AJAX Handlers Added
- `auctionforge_sync_log_poll` — Incremental log polling for live console
- `auctionforge_sync_queue_status` — Queue counts + recent jobs for dashboard
- `auctionforge_sync_enqueue` — Admin-triggered sync actions (full_sync, sync_auction, spawn_worker)

### Fixes
- **cURL timeout** — BidSpiritRequest now enforces a 30-second timeout (was unlimited/0), preventing hung connections from blocking the sync pipeline.

### Files Changed
- **Modified:** `auctionforge.php`, `inc/coreIncludes.php`, `inc/controllers/AuctionsAPI.php`, `inc/BidSpiritRequest.php`, `admin/view/sync-status.php`, `crontasks/setup.sh`
- **Created:** `inc/sync/SyncQueue.php`, `inc/sync/SyncLogger.php`, `inc/sync/CatalogSyncWorker.php`, `inc/sync/ItemUpdateWorker.php`, `crontasks/syncWorker.php`, `crontasks/syncSafety.php`

---

## [2.7.3.0]

### Fixes
- Various stability improvements and bug fixes.

## [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`.

## [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.

## [2.7.0.9]

### Fixes
- Fixed fatal error during plugin install/upgrade: `class-stats-database.php` require failure.

## [2.7.0.0]

### New Features: Advanced Lot Analytics
- Conversion Tracking, Peak Viewing Times, Pre-Sale vs Live Comparison, Category Performance, Share Analytics, Search → View Funnel, Click-Through Tracking, CSV Export, Hot Lot Badge.
- New DB table: `af_lot_shares`. New columns on `af_lot_analytics`. DB version bumped to 20.

## [2.6.0.0]

### New Feature: Lot Analytics
- Comprehensive lot view and favorite tracking system with admin dashboard.

## [2.5.0.0]

### New Features
- **Site Statistics System** — Full website analytics built into the plugin admin.
- **Recently Viewed Items** — Profile page tab showing last 20 viewed lots per user.
