# AuctionForge Changelog

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

---

## [3.6.0.4] — 2026-04-12

### Auction Approval Modal Redesign

#### Changes
- **Removed BidSpirit Terms of Use** — The generic BidSpirit "Terms of Use" section that appeared at the top of the non-credit-card approval modal has been removed. The modal now shows only the auction house's own legal agreements managed within the plugin.
- **Renamed Modal Title** — Changed "Terms of conditions" to "Terms & Conditions".
- **Larger Scroll Area** — The Terms & Conditions scroll box is now 350px tall (up from 200px) for easier reading.
- **Proportional Section Heights** — Refund & Return Policy and Chargeback Waiver sections use a shorter 260px scroll box to visually distinguish them from the primary Terms & Conditions.

#### Files Changed
- **Modified:** `templates/js-chunks/approval-info.php`, `assets/js/app.js`

---

## [3.6.0.2] — 2026-04-10

### Webhook Token Generator

#### Features
- **Token Generator Widget** — Added a 16-character random token generator directly below the "Webhook auth token" field on the Settings page. Includes a "Generate Token" button and a clipboard copy icon with visual feedback.

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

---

## [3.6.0.1] — 2026-04-10

### Patch

#### Changes
- Version bump.

---

## [3.6.0.0] — 2026-04-10

### Legal Agreements for Auction Approval

#### Features
- **Terms & Conditions** — A mandatory, scrollable Terms & Conditions document is now displayed during the auction approval process. Bidders must scroll to the bottom and check an agreement box before their approval request is submitted. Always enabled.
- **Refund & Return Policy** — An optional, scrollable Refund & Return Policy can be enabled in Settings. When enabled, bidders must read and accept it during auction approval.
- **Chargeback Waiver** — An optional, scrollable Chargeback Waiver can be enabled in Settings. When enabled, bidders must read and accept it during auction approval.
- **Admin Management** — New "Legal Agreements" page under the Bidspirit admin menu with tabbed TinyMCE editors for each agreement type. Includes automatic version numbering (incremented on content change) and full version history.
- **Consent Logging** — Every agreement acceptance is logged to a dedicated database table (`af_user_agreement_log`) with the agreement type, version, user email, IP address, user agent, auction ID, and timestamp.
- **User Tracking Integration** — Agreement events (`terms_agreement`, `refund_agreement`, `chargeback_waiver`) are also recorded in the existing IP tracking system alongside registration, login, and bid events.
- **Placeholder Content** — Default sample content is seeded on plugin activation for all three agreement types.

#### Settings Added
- **Enable Refund & Return Policy** — Toggle to require bidders to accept the refund policy during auction approval.
- **Enable Chargeback Waiver** — Toggle to require bidders to accept the chargeback waiver during auction approval.

#### Database
- New table: `af_legal_agreements` — Current active agreement content per type
- New table: `af_legal_agreement_versions` — Version history archive
- New table: `af_user_agreement_log` — User consent records

#### Files Changed
- **Modified:** `auctionforge.php`, `inc/controllers/Settings.php`, `inc/controllers/BidspiritViews.php`, `inc/ip-logger.php`, `templates/js-chunks/approval-info.php`, `assets/js/app.js`
- **Created:** `inc/legal/class-legal-agreements.php`, `admin/view/legal-agreements.php`

---

## [3.5.0.6] — 2026-04-08

### Bid Event Tracking

#### Features
- **Bid Tracking** — All successful bids (regular, catalog, pre-sale buyout, post-sale purchase) now fire `afLogIP("bid", bidspiritUser)` to capture the bidder's full identity in the user tracking table.
- **Whitelist Update** — Added `bid` to the allowed `event_type` values in both the local plugin and the central WPDeploy API.

---

## [3.5.0.5] — 2026-04-08

### Self-Healing Database Schema

#### Features
- **Auto-Migration** — The plugin now auto-detects and adds any missing tracking table columns on version upgrade, ensuring sites with older schemas seamlessly migrate without manual SQL.

---

## [3.5.0.4] — 2026-04-08

### API Key Auto-Healing

#### Features
- **Seamless Self-Registration** — The WordPress plugin now autonomously regenerates and saves a fresh API Key whenever WPDeploy rejects a payload with an `api_key_collision` error. This guarantees zero-downtime security handovers when developers clone instances to proxy/staging servers.

---

## [3.5.0.3] — 2026-04-08

### API Key Collision Defense

#### Features
- **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] — 2026-04-08

### Modal Trigger Hotfix

#### Fixes
- 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] — 2026-04-08

### Hotfix

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