Plugins Documentation How It Works About Support My Account Browse Plugins

Product Changelog

Every update across every Bodholdt Labs plugin, newest first. We ship often, and we write it all down.

Subscribe via RSS
Bodholdt Licensing v6.4
  • 24 UI/UX and accessibility fixes
  • Inline error banners replacing alert()
  • Order confirmation flow
  • Loading spinner
  • Pagination and search for license management
  • License key lookup in customer portal
  • Trial user portal messaging
  • WCAG 2.1 status badges
Bodholdt Licensing v6.3
  • 10 security fixes including path traversal prevention
  • Proxy-aware rate limiting
  • Webhook elseif chain optimization
  • Strict comparisons throughout
Bodholdt Licensing v6.2
  • 16 security fixes
  • JSON domain storage (replaced PHP serialize)
  • Webhook idempotency guard
  • CORS restriction to known origins
  • Stripe lazy loading
Bodholdt Licensing v6.1
  • 12 security fixes
  • CSRF protection on all forms
  • XSS prevention with proper escaping
  • SQL injection prevention with prepared statements
  • Rate limiting system
Bodholdt Tickets v0.33.4
  • Free (wp.org) edition: reworded the customer portal's "Linked emails" copy so it reads as consolidating your tickets across email addresses (no licensing/paid-support phrasing), and finished the wp.org readme (screenshots + a clean free-launch changelog). No behavior change.
Bodholdt Tickets v0.33.3
  • Code quality: annotate the custom-table database queries (the plugin owns its own `wp_bt_*` tables, which have no core API) with justified `phpcs:ignore` markers, add `wp_unslash()` to the remaining admin save paths, and trim the short description — clears the WordPress.org Plugin Check warnings. No behavior change.
  • Free (wp.org) build: regenerate the translation template for the free slug/text domain so the shipped `.pot` matches `bodholdt-support-tickets` (no stale licensing strings).
Bodholdt Tickets v0.33.2
  • Hardening: escape upgrade-nag output in the agents/plan admin views and add `wp_unslash()` + key sanitization to the Settings and Appearance save paths.
  • Fix: pass bound query parameters to `$wpdb->prepare()` via argument spread in the ticket-queue query so replacement counts match (no behavior change).
  • Free (wp.org) build: the slug rename now also rewrites `assets/admin.css`, so the free edition's admin styling is correctly scoped and no longer renders unstyled.
Bodholdt Tickets v0.33.1
  • Desk SPA: surface SLA state in the agent desk — an "Overdue" / "At risk" badge on queue rows, an SLA quick-filter (Overdue / At risk) in the inbox, and first-response / resolution due times with their on-track / at-risk / overdue state in the ticket detail panel. (Pro; the desk shows nothing SLA-related on the free plan.)
Bodholdt Tickets v0.33.0
  • New (Pro): SLA management + automation. Set first-response and resolution targets per priority (Urgent / High / Normal / Low), define your business hours (working days, open/close, timezone, or count 24/7), and every new ticket is stamped with due dates. The desk flags tickets that are *at risk* or *breached*, and an hourly check emails the assigned agent and admin the moment a target is missed.
  • New (Pro): round-robin auto-assignment. Tickets routed to a department are automatically handed to the next agent in that department.
  • SLA due dates recompute automatically when a ticket's priority changes. (The basic SLA *report* remains available on the free plan.)
Bodholdt Tickets v0.32.1
  • Rebuilt the Agent Support Desk SPA and refined the in-admin help and upgrade prompts.
  • Packaging: the WordPress.org free build now ships a self-contained free edition (Pro-only code physically stripped) and is available as a self-served Lite download.
Bodholdt Tickets v0.32.0
  • New, more generous Free + a clearer Pro. The plan model is now free-vs-Pro: paid tiers differ only by how many sites they cover. Free now includes up to 3 agents, unlimited tickets, the support form & block, the customer portal, custom fields, private notes, canned replies, CSAT, gamification, categories, the Claude (bring-your-own-key) AI co-pilot, the embeddable widget, and basic/aggregate reports.
  • Pro (any paid plan) adds: unlimited agents, inbound email-to-ticket piping, departments & routing, advanced per-agent/per-department reporting, and license-aware customer context in the desk.
  • Inbound email intake and the licensing-aware customer context are now gated to paid plans; the free plan is capped at 3 agent seats (upgrade to any paid plan for unlimited agents).
Bodholdt Tickets v0.31.0
  • In-app bug reports (authenticated channel). A new `bt/v1/app-report` endpoint lets a companion mobile app file tickets on behalf of the logged-in user — no typed email, identity comes from the authenticated account. Reports land in your desk on the new `app` channel and reply by email like any other ticket.
  • Beta-tester allowlist. Submission is gated on a per-user Beta tester capability, toggled from the user-edit screen (Users → edit a user → "Bodholdt Speed Test — Beta"). Only allowlisted accounts can file reports. The `app` channel bypasses the free-tier cap and the paid-support gate, so it stays fully decoupled from licensing.
Bodholdt Tickets v0.30.0
  • Agent email alerts. Get an email the moment a customer opens a ticket or replies — so your team is notified even when nobody has the Support Desk open. A reliable fallback to the desk's browser/desktop alerts (which require each agent to opt in per device).
  • Turn it on under Tickets → Agent notifications: choose whether to alert on new tickets, new replies, or both; send to all agents or only the assigned agent/department; add extra recipients (a shared inbox or on-call alias); and send a test alert to confirm delivery. Off by default.
Bodholdt Tickets v0.29.1
  • Appearance preview fix. The settings-page brand preview now faithfully reflects light/dark mode (the admin dark styling was bleeding into the live preview's input fields). Visual-only.
Bodholdt Tickets v0.29.0
  • White-label your customer-facing surfaces. The contact form, customer portal, and floating widget now adopt *your* brand instead of Bodholdt's — driven by a single accent colour with clean, neutral defaults. Semantic ticket-status colours keep their meaning.
  • New Appearance settings. Pick a brand colour, light/dark/auto mode, corner style, and font from the Tickets settings page, with a real live preview. Applies across the form, portal, and widget at once. (The agent Support Desk is intentionally left on its own dark theme.)
Bodholdt Tickets v0.28.0
  • Refreshed admin styling. The Tickets settings screen now uses the shared Bodholdt Labs dark admin design system — clearer headings and labels, readable inputs and tables, a unified button style, and consistent focus states. No functional change.
Bodholdt Tickets v0.27.0
  • Choose your support model. New setting runs lifetime support as non-expiring credits (tokens), a fixed time window, or both.
  • Build your own credit packs. Define any number of packs (credits + price + your own Stripe Payment Link) instead of a fixed single/3-pack.
  • Set how many non-expiring credits each lifetime license grants, by site tier.
Bodholdt Tickets v0.26.2
  • See your support credits. The My Tickets portal now lists your available support credits with a per-product breakdown, so you always know what you have and which product each credit applies to.
Bodholdt Tickets v0.26.1
  • Renamed to Bodholdt Tickets. The product is now "Bodholdt Tickets" and the wp-admin menu reads "Tickets." No functional changes - your license, data, and settings are untouched.
Bodholdt Tickets v0.26.0
  • Product-scoped support credits. Lifetime support credits are now redeemable only for support on the product they were purchased with; manually-granted/comped credits remain universal.
Bodholdt Tickets v0.25.0
  • Custom ticket fields. Add your own fields (text, dropdown, checkbox, email, number, and more) to the support form and the new block - values arrive attached to the ticket.
  • Gutenberg support-form block. Drop the support form on any page with a block, alongside the existing [bodholdt_support_form] shortcode.
  • Lifetime support credits. Lifetime license purchases now automatically receive non-expiring support credits, ADDED to any credits you already have (never overwritten).
  • More desk power. File attachments on tickets (customer + agent), a canned/saved-replies picker, one-click ticket deletion, a personal "My stats" tab, and a fully responsive Support Desk.
  • Accessibility, i18n, and reliability improvements throughout.
Bodholdt Licensing v10.28.0
  • Pricing: Standardized every product page's pricing cards on the Bodholdt Tickets layout. Product lines that ship a free edition (Backups and Atelier, alongside Tickets) now show the Free card first, in a four-card row, with a "Get it free" button that links to that product's free download on the same page. Bodholdt Licensing has no free edition and keeps its three cards.
  • Pricing: Aligned the Free card so its feature checklist lines up with the paid cards across the row, and replaced the inline price subtext with a short "Free forever:" lead-in that mirrors the paid cards' "Everything in [tier], plus:" wording.
  • Pricing: Reworked every paid tier's feature list so each one lists only what it adds over the tier directly below it, instead of repeating the lower tier's features. The full per-feature comparison table still carries the complete detail.
  • Pricing: Clarified that support credits apply to one-time (Lifetime) plans only. The comparison row now reads "Support credits (Lifetime plans only)," with a note that annual and monthly plans include email support while active.
Bodholdt Licensing v10.27.0
  • Pricing: Aligned the Bodholdt Atelier pricing copy and feature comparison with Atelier 1.7.0, which moved recurring patron subscriptions (with content drip) and commissions (with milestone payments and booking) from the free Lite build to Solo and above. The Free Lite card and matrix now describe the core one-off storefront only; Solo's headline value is the two recurring/commission money-makers. Pro (Foundry, Marketplace, Publish API with the Adobe Lightroom plugin) and lifetime support credits are unchanged.
Bodholdt Licensing v10.26.1
  • Fix: On the Bodholdt Atelier product page, the "See full feature comparison" toggle rendered the Backup feature matrix instead of Atelier's. The per-product pricing block now resolves the correct Atelier comparison family.
Bodholdt Licensing v10.26.0
  • Bodholdt Atelier added to the pricing page. The /buy-plugins/ page now lists Bodholdt Atelier alongside Backups, Licensing, and Bodholdt Tickets, with a Free Lite card plus Solo, Pro, and Agency tiers, and a full feature comparison.
  • Free Lite (self-hosted on your own Stripe or PayPal, 1 site, updates by manual download) includes all core commerce: the 13 work types, checkout, certificates of authenticity and provenance, per-buyer watermarking with signed expiring downloads, commissions, patrons, EU VAT and IOSS, the music pipeline, the collectors CRM, royalty splits, and the white-label storefront.
  • Solo adds a license with automatic updates and direct support. Pro adds Foundry (a multi-artist collective with Stripe Connect payouts and a leaderboard), Marketplace (cross-artist discovery), and the Publish API with the Adobe Lightroom plugin. Agency is everything in Pro for up to 25 sites.
Bodholdt Licensing v10.25.0
  • Lite-edition entitlement gate (internal). Adds the runtime capability layer for a future free, capped edition. No customer-facing change for the full edition — behavior is identical to 10.24.17.
  • New single source of truth (`cls_edition()` / `cls_lite_can()`): the Lite cap is opt-in via an explicit `CLS_EDITION` build constant and is hard-vetoed for any self-hosted/licensed server or recognised paid tier, so a full install can never be silently capped.
  • Under Lite only: 1 product, 1 site per license; Reports, Activity Log, the management REST API, the activity-log export, and bundles are held behind the paid edition. Checkout, license issuance/validation, the customer auto-updater, the My-Keys portal, the SDK generator, and secure vault downloads always function in both editions.
Bodholdt Licensing v10.24.17
  • Free-funnel copy fixes. Removed the stale "coming soon" from the Bodholdt Tickets pricing-page section subtitle and the single-product ticketing pitch — the free (Lite) edition is live and self-served, so both now close with "Download it free."
  • Cleaned up the self-serve download intro: "The free version is yours. No card, no license key." (dropped the em-dash, US spelling).
  • Copy only. No payment products, pricing, identifiers, or licensing behavior changed.
Bodholdt Licensing v10.24.16
  • Bodholdt Tickets free edition is now self-served. The Bodholdt Tickets Free card on the pricing page links straight to the free download instead of showing a "Coming soon" label, pointing at the Tickets product page's free-download section.
  • The self-serve email-to-download flow (and its stable free-download link) is now authorized for Bodholdt Tickets.
  • No payment products, pricing, identifiers, or licensing behavior changed.
Bodholdt Licensing v10.24.15
  • Bodholdt Tickets pricing refreshed. The Bodholdt Tickets pricing cards and feature comparison now reflect the current model: a generous free tier (up to 3 agents, unlimited tickets, with Claude-BYOK AI drafting included), and three paid tiers that all unlock the same Pro features (unlimited agents, email-to-ticket piping, departments and routing, advanced reporting, and licensing-aware customer context), differing only by sites: Solo 1 site, Studio 5 sites, Foundry 25 sites.
  • Free tier shown as "Coming soon." The free edition is self-served from bodholdtlabs.com and is coming soon, so its card shows a "Coming soon" label rather than a download link.
  • Copy-only refresh of the pricing page. No payment products, pricing, or licensing behavior changed.
Bodholdt Licensing v10.24.14
  • "Get it free" is back on the pricing page. The backup plugins' Free card on the pricing page now links straight to the free download again (it had been a non-clickable "Coming soon" placeholder while the self-serve download was being built). It points at the matching product page's free-download section, and follows your Google Drive / OneDrive selection.
  • Free download form now appears on the backup product pages. Each backup product page now shows the "Or start free with the Lite edition" download form so visitors can grab the free edition right where they're deciding.
  • This release adds no payment products and changes no pricing.
Bodholdt Licensing v10.24.13
  • Free download replaces the trial. The no-card trial has been dropped — the free version *is* the trial. The self-serve sign-up no longer issues any licence: after you verify your email with a 6-digit code, we simply email you a download link for the free edition of Bodholdt Backup for Google Drive or Bodholdt Backup for OneDrive. The full Pro edition stays paid-only.
  • Simpler form. The sign-up form is now a single "Get the free download" step (email → code → emailed link); the Free-vs-Trial choice is gone. The download link still arrives by email only.
  • Abuse protection hardened (security review). Verification codes are now capped per email address (one per minute, a few per day) in addition to the existing per-IP limit; there is a daily ceiling on total free-download emails; and the Cloudflare visitor-IP header is now only trusted when the request genuinely arrives through Cloudflare, so the rate limits can't be bypassed by spoofing it.
  • This release adds no payment products and changes no pricing.
Bodholdt Licensing v10.24.12
  • Self-serve sign-up for the backup plugins (foundation). A new public, email-verified endpoint can issue either the free Lite edition or a no-card 14-day Pro Trial for Bodholdt Backup for Google Drive and Bodholdt Backup for OneDrive, then email the license key and a download link. The visitor first requests a 6-digit code by email (using the same verification step the customer portal already uses), then enters the code and picks Free Lite or the Pro Trial.
  • Built to be safe by design. The product is restricted to an allowlist (the two backup plugins), the license tier is set by the server — never by the browser — and the key and download link are sent by email only (never shown on-screen). Requesting Lite a second time re-sends your existing key rather than creating a duplicate; the 14-day Pro Trial can only be claimed once per plugin per email. The on-screen confirmation is identical whether or not you already had a license, so the form can't be used to probe which emails are customers. The endpoint is rate-limited.
  • New shortcode `[cls_self_serve product="…"]` renders the two-step sign-up form for a future landing page. It is not yet placed on any page.
  • This release adds no payment products and changes no pricing; the trial/Lite downloads reuse the same secure download the paid editions already use.
Bodholdt Licensing v10.24.11
  • Pricing page: the backup "Free" card's download button pointed at a WordPress.org listing that isn't published yet (it dead-ended in an empty search), and a feature bullet mentioned a WordPress.org support forum that doesn't exist. The button is now a non-clickable "Coming soon" until the free edition is available, and the forum bullet is gone.
Bodholdt Licensing v10.24.10
  • Pricing FAQ copy: the "Do you offer a free trial?" answer no longer claims a live WordPress.org free tier (the free edition isn't published yet) — it now points to the 14-day money-back guarantee. Minor punctuation/em-dash cleanup across the pricing FAQ.
Bodholdt Licensing v10.24.9
  • Customer portal polish: replaced the key emoji on the "My Keys" button with a clean inline icon that follows your brand accent colour.
Bodholdt Backup for Google Drive v6.26.0
  • More emerald polish. "Start Backup Now" cloud icon is now pure white; the "Your Backups" table (column headers, file/Download/Restore icons, row hover) is now emerald instead of cyan/purple; in-app "Upgrade to Pro" notices and the troubleshooting path now use the emerald accent.
  • Logs toolbar. The "Apply" filter button is now a gradient button and "Clear History" is clearly marked red as a destructive action.
Bodholdt Backup for Google Drive v6.25.0
  • Admin polish. Dashboard "Success" and "Connected" status pills now use the emerald theme accent; "Refresh Estimate", "Test Connection", "Send Test Email", and "Send Test Notification" are now gradient buttons; and "Reset to Defaults" is now clearly marked red as a destructive action.
  • Fixed "Your Backups" showing empty. The plugin could read a stray duplicate "Bodholdt Backups" folder in Google Drive and report no backups even when backups existed. It now reliably resolves the original folder (oldest match) and will no longer create a duplicate folder if a Drive lookup briefly fails.
Bodholdt Backup for Google Drive v6.24.0
  • Consistent buttons. The "Start Backup Now" button now uses the same green gradient as the "Save Settings" button, so the primary actions match across the admin.
Bodholdt Backup for Google Drive v6.23.0
  • Green admin theme. The Google Drive admin now uses an emerald-green accent that matches its product page — so it reads as distinct from the cyan Bodholdt Backup for OneDrive admin at a glance. Same layout and dark canvas; accent colour only.
Bodholdt Backup for Google Drive v6.22.0
  • Admin styling now sourced from the shared Bodholdt Labs design system. The admin UI consumes the canonical `bod-admin.css` (the same dark-admin stylesheet shipped across Bodholdt Labs plugins) instead of a plugin-local copy, so the look stays consistent and future polish lands everywhere at once. This release brings the Google Drive admin up to full parity with the OneDrive edition: entrance animations on cards/modals/toasts, standardized title/progress gradients, a tooltip layering fix, the upsell/locked-feature polish, and an accessible purple text colour. No functional or behavioural change.
Bodholdt Backup for Google Drive v6.21.0
  • Pro edition with licensed auto-updates. This is the paid, off-directory build of Bodholdt Backup for Google Drive, distributed by Bodholdt Labs. It enables in-dashboard automatic updates delivered from the Bodholdt Labs licensing server when a valid license key is active (license key + site URL + product identifier only; never site content or backup data).
  • Selective restore available on every paid tier, including Solo — pick exactly which components (database, system files, themes, plugins, uploads) to restore. Retention is uncapped on all tiers (bounded only by a per-install sanity limit).
  • Slack/Discord webhook notifications for backup success/failure, restore-initiated, and stale-backup watchdog events (Pro and Bundle tiers).
  • Hardening (parity with the directory edition): first-party download streaming now uses the WordPress HTTP API; admin-notice scripts moved to `wp_add_inline_script`; staging/uninstall paths resolved via `wp_get_upload_dir()`; installer CSRF/lockfiles relocated to the system temp directory with a table-prefix validation guard; all request inputs run through `wp_unslash()` before sanitization.
  • Added an "External services" section to this readme documenting the Google Drive / Google OAuth endpoints, the Bodholdt Labs licensing/update server, and the WordPress.org secret-key (salt) request used during cross-server migration.
Bodholdt Backup for Google Drive v6.20.2
  • Fixed: Duration column in the Logs view showed a stray trailing "s" (e.g. "2 minutess" instead of "2 minutes"). The duration string from `human_time_diff()` already includes the unit word, but the Logs table cell appended an extra `'s'` — a leftover from when duration was rendered as a numeric-seconds value. The cell now renders the duration string as-is. Cosmetic only — no data, scheduling, or backup behavior changes.
Bodholdt Backup for Google Drive v6.20.1
  • Fixed: footer line missing on installs that run third-party plugins which globally blank the WP admin footer. Some plugins (e.g. Taxonomy CSV Import/Export) register `__return_empty_string` against `admin_footer_text` at priority 11 across every admin page — which silently wiped our v6.20.0 footer line (Bodholdt Backup for Google Drive v6.X.X · Documentation · Support). Our filter now runs at priority 99, so it executes last and the footer renders as designed regardless of any other plugin's behavior.
Bodholdt Backup for Google Drive v6.20.0
  • UI: version moved out of the page title. The big "BODHOLDT BACKUP FOR GOOGLE DRIVE V6.X.X" plugin-page heading is now just the product name + the tier badge ("FREE" / "Pro" / etc.). The version still appears, but now in the WordPress admin footer alongside Documentation and Support links — discreet and out of the way. Aligned with how WordPress core, WooCommerce, Yoast, and most established plugins handle this.
Bodholdt Backup for Google Drive v6.19.1
  • Brand consistency follow-up. The License sub-page heading still showed the short-form "Bodholdt G-Drive" label after the v6.19.0 rebrand. Now reads "Bodholdt Backup for Google Drive — License" to match the rest of the plugin. No behavior change.
Bodholdt Backup for Google Drive v6.19.0
  • Renamed to "Bodholdt Backup for Google Drive." Adopts the trademark-safe "for [destination]" naming pattern across all customer-facing surfaces (plugin header, admin menus, dashboard, email subjects, logs). Functionally identical — no settings or backups affected.
  • Setup wizard — inline OAuth setup guide. Step 2 of the first-run wizard now spells out exactly how to create the OAuth Client ID and Client Secret in Google Cloud Console — including the redirect URI you need to paste, inline with a copy-to-clipboard helper. Previously this guidance lived only in the Settings tab, and the wizard alone left first-time users without enough information to complete the connection.
  • Setup wizard — improved "Testing on a local site?" callout. The advisory now correctly explains Google's policy that redirect URIs must be either `http://localhost` or HTTPS at a public TLD (`.com`, `.org`, etc.), so `.local` development sites (Local by Flywheel, MAMP) are rejected even with HTTPS enabled. Recommends using Local's "Live Link" feature for an HTTPS public URL when testing.
  • Tested up to: WordPress 7.0. Verified compatibility with the current stable release.
  • Honest claims. Softened a few storefront descriptions to be evergreen rather than time-specific ("accessibility-minded" rather than a literal compliance grade; "security-audited each release" rather than a specific score) so they stay accurate without periodic updates.
  • wp.org Contributors handle updated to `bodholdtlabs` in preparation for wp.org plugin directory submission.
Bodholdt Backup for Google Drive v6.18.0
  • Per-site backup folders. Each site now backs up to its own uniquely-named folder in your Google Drive (identified by a per-site ID), so multiple WordPress sites sharing one Google account no longer write into the same folder. If a shared folder from an earlier version is detected, the plugin moves this site to its own folder and shows a one-time notice — nothing is deleted and your existing backups stay untouched.
Bodholdt Backup for Google Drive v6.17.2
  • Licensing fix + brand consolidation. License activation, validation, and auto-updates now point at the correct server (bodholdtlabs.com) and use the correct product reference, so license keys validate reliably (a mismatch previously prevented validation). Author byline, support email, and account links updated to Bodholdt Labs / bodholdtlabs.com.
Bodholdt Backup for Google Drive v6.17.1
  • Retention tier fix. Solo and Agency licenses now get their correct backup-retention limits (Solo 50, Agency unlimited up to the system maximum), and Agency now includes selective restore and Slack/Discord notifications. Previously these tiers fell back to the Free 10-backup limit because the entry tier is issued as "default" and the top tier as "agency" — both are now recognized. Pro and Bundle are unchanged.
Bodholdt Backup for Google Drive v6.17.0
  • Fun + accessibility + consistency pass. Brings the celebration moments, accessibility, and copy up to full parity with Bodholdt Backup for OneDrive, plus a few new-for-both delights.
  • New: cumulative "backups protected" stat. The Backup Health card now shows a running total — "🛡️ N backups protected · X GB kept safe" — counted from a dedicated counter so it survives clearing your activity log.
  • New: first-backup celebration. Your very first successful backup now gets a bigger one-time celebration ("Your first backup is done — your site is officially protected! 🛡️").
  • New: restore-complete celebration. Finishing a restore now greets you with a gentle celebration and a "Site restored — welcome back." confirmation.
  • Richer backup-complete celebration — multi-burst branded confetti, a green-to-teal glow on the progress bar, and a success notification (matching Bodholdt Backup for OneDrive).
  • Accessibility: confetti now respects "reduce motion." All celebration animations are skipped when your system requests reduced motion. Tab navigation also gained proper ARIA tablist semantics for screen readers.
  • Friendlier first-run guidance: the connect-success banner is now warmly themed with a "Run First Backup →" button; the Start Backup button is disabled with a "Connect your Google Drive first — Open Settings" prompt until you've connected; and a "Your site stays online during the backup." reassurance now sits under the button.
  • Clearer empty states + copy: the activity log empty state is now two reassuring lines; health-card placeholders read "Ready to start" / "Awaiting first backup"; and destructive confirmations now state exactly what happens and what's reversible.
  • Help sidebar now links to the plugin homepage and email support. Page heading standardized to "Bodholdt Backup for Google Drive."
Bodholdt Backup for Google Drive v6.16.1
  • Cross-plugin parity follow-ups.
  • "Reset to Defaults" now restores a sensible Daily 3:00 AM backup (both backup plugins now reset to the same default). And a scheduled backup on a site that hasn't connected its cloud account yet now skips quietly, instead of emailing a daily "backup failed" alert.
  • Removed the extra pre-backup confirmation dialog — clicking "Start Backup Now" begins immediately, matching Bodholdt Backup for OneDrive's one-click flow. The "Force Full Backup" option is still right there as an inline checkbox above the button.
Bodholdt Backup for Google Drive v6.16.0
  • Steve Jobs / Grandma Test Pass — Rounds 2-5 (error states, settings parity, micro-interactions, visual polish). Brings the plugin to full feature/UX parity with Bodholdt Backup for OneDrive. Bundles three internal batches:
  • *Error handling (R2):* OAuth failures now show a friendly, recoverable banner instead of a blank WordPress error page; an expired or revoked Google Drive authorization now prompts you to reconnect instead of showing "no backups yet"; a dropped network request during a backup or restore no longer freezes the progress bar silently (you get a clear error + retry path); a mistyped notification email is no longer saved silently; cancelling at Google's consent screen is handled gracefully; backup-download errors are now friendly with a back link.
  • *Settings (R3):* added Notification-Email and Backup-Type help tooltips, a copy-paste real-cron helper, a friendlier Google Drive destination display, and clearer "Backups to Keep" / "Migration Tools" / file-exclusion copy.
  • *Polish (R4/R5):* modern navigation guard during in-progress backups, a green "saved" confirmation, and all admin brand colors moved onto the shared design-token system (so the warning color is now consistent across both backup plugins). Build pipeline excludes editor/OS cruft from the distributed zip.
Bodholdt Backup for Google Drive v6.15.0
  • Fixed: Staging directory now has a graceful fallback when `/var/lib/bodholdt-staging/` isn't writable. Pre-v6.15.0 the plugin defaulted `BODHOLDT_GDRIVE_DIR` to `/var/lib/bodholdt-staging/gdrive/<random-suffix>/` with no fallback — a path that only exists on operator-prepared hosts. On any host without root access (most managed WordPress hosts, every local development environment, every WP.org reviewer install), the `mkdir()` call returned false, the next `fopen()` failed silently, and the backup or restore died mid-flight with the AJAX progress indicator simply disappearing at "Fetching Download link…" — no error to the user. v6.15.0 adds a three-tier fallback hierarchy resolved at plugin-load time: (1) `/var/lib/bodholdt-staging/gdrive/<suffix>/` if writable (preserves the post-2026-05-05 security model on operator-prepared hosts), (2) `sys_get_temp_dir() . '/bodholdt-staging-gdrive/<suffix>/'` if `/var/lib/` isn't writable (system temp, ephemeral but still isolated from the web document root), (3) `wp-content/uploads/.bodholdt-staging-gdrive/<suffix>/` as a last resort with three hardening layers (an `.htaccess` deny-all rule for Apache, an `index.php` returning HTTP 403 as defense-in-depth, and a docs-recommended nginx `location` deny rule in the FAQ). Each fallback transition is logged via `error_log()` so operators on hardened hosts can see why the plugin moved off the primary. Override via `define('BODHOLDT_GDRIVE_DIR', __DIR__ . '/your/custom/path/')` in wp-config.php to bypass the resolution entirely. Plan §4.5.13.
  • Fixed: Backup engine was silently omitting WordPress drop-ins (`object-cache.php` / `advanced-cache.php` / `db.php` / `maintenance.php`) at the root of `wp-content/`. v6.14.1 closed the matching gap for `wp-content/mu-plugins/` and v6.14.2 closed the matching gap on the restore side for drop-ins, but the backup engine itself never added drop-ins to the zip — its scope-dirs helper enumerates directories only, and drop-ins are single files. v6.14.2's restore-side `is_file()` guard masked the gap by degrading quietly (file missing from temp dir → no error → drop-in silently absent from the destination). v6.15.0 adds an unconditional drop-ins block to the backup engine (symmetric to v6.14.1's mu-plugins always-include treatment) with `is_file()` guards so installs without drop-ins degrade quietly. Drop-ins now ride through end-to-end on the backup/restore round-trip. Plan §4.5.19.
Bodholdt Backup for Google Drive v6.14.2
  • Fixed: Restore engine was silently dropping `wp-content/mu-plugins/`, `wp-content/languages/`, and WordPress drop-ins (object-cache.php / advanced-cache.php / db.php / maintenance.php). v6.14.1 closed the matching backup-side gap so mu-plugins now ride along inside the zip — but the restore engine still only copied `themes/`, `plugins/`, and `uploads/` from the extracted backup to the destination's `wp-content/`. Result: any disaster-recovery restore from any prior version landed a site without its must-use plugins (custom auth handlers, debug-log filters, multisite-shared utilities, etc.) and without its drop-ins (Redis object cache, full-page cache, custom DB layer, maintenance-mode override). The restore engine now treats `mu-plugins/` and `languages/` like the database — always copied, no user-facing scope toggle — and copies the four standard wp-content drop-ins when they're present in the backup. Surfaced 2026-05-17 night during the §4.5.10 GDrive end-to-end retest. Plan §4.5.15.
  • Fixed: Restore engine no longer unconditionally drops your other Bodholdt backup plugin. Prior versions hard-skipped both `bodholdt-google-drive-backup` AND `bodholdt-onedrive-backup` directories during restore. The self-skip is necessary (a plugin can't safely overwrite itself mid-execution), but the sibling-skip meant customers running both plugins as belt-and-suspenders backup would lose one on every restore. The restore engine now skips the sibling only when it's currently network-active (i.e. running and could collide); installed-but-inactive siblings are restored normally. Plan §4.5.18.
Bodholdt Backup for Google Drive v6.14.1
  • Fixed: `wp-content/mu-plugins/` was silently omitted from every backup. The backup engine's scope-dirs helper enumerated four directory scopes — `core` (wp-admin + wp-includes only), `themes`, `plugins`, and `uploads` — but had no entry for mu-plugins. Result: every GDrive backup zip was missing must-use plugins entirely. Restoring from one of these backups would leave the destination site without any of its mu-plugins (custom auth handlers, debug-log filters, multisite-shared utilities, etc.), silently corrupting site behavior. Surfaced 2026-05-17 during the §4.5.5 GDrive parity retest — OneDrive's wholesale ABSPATH `core` scan picked them up as a side effect, masking the bug across plugins until this retest. Fix: mu-plugins are now treated like the database — always included, no user-facing scope toggle, no way to accidentally omit. Plan §4.5.11.
  • Note on Bodholdt Backup for OneDrive: Bodholdt Backup for OneDrive v5.14.0 is not affected. Its `core` scope walks `ABSPATH` wholesale with skip-prefixes for plugins/themes/uploads, so mu-plugins were already included via the wider walk. No OneDrive hotfix is required.
Bodholdt Backup for Google Drive v6.14.0
  • New: Welcome step added to the setup wizard. The wizard now opens on a friendly Welcome screen ("Let's get your site backed up to Google Drive in just a few steps") instead of dropping the user straight into a Client ID / Client Secret form. The 3-step shape becomes 4-step: Welcome → Connect Google Drive → Schedule → Run First Backup. Mirrors the new Bodholdt Backup for OneDrive v5.14.0 wizard structure for cross-plugin consistency. Closes Steve Jobs pass Round 1. P1-B + P1-C.
  • New: Wizard JS state machine refactored. Step navigation is now driven by a single `showWizardStep(n)` helper that handles step visibility, dot indicators, and the step-counter text consistently. The previous per-step JS handlers had subtle drift; this refactor unifies them.
Bodholdt Backup for Google Drive v6.13.0
  • New: "Set up" link on the Plugins page. After activating, a "Set up" / "Backups" quick-link now appears next to Activate/Deactivate in the WordPress Plugins list, giving you one-click access to the dashboard. Standard WP pattern; we should have shipped this in v4. Steve Jobs pass Round 1, P1-A.
  • New: Friendlier setup-guide language. Wizard welcome copy and the OAuth Setup Guide on Settings have been rewritten to drop "Google Cloud credentials" / "Google Cloud Console" jargon in favor of consumer-friendly framing ("free Google project", "Google Cloud (free)") that explains *why* each step is needed. Technical terms ("Client ID", "Client Secret", "Redirect URI") stay where they're necessary, but the welcome copy no longer reads like enterprise IT. P1-D.
  • New: OAuth consent-screen step is now correctly ordered. The Setup Guide previously listed "Configure Consent Screen (if prompted)" as Step 4, but Google requires consent-screen configuration before they'll let you create credentials at all — so it's *always* prompted on a new project, not optional. The step is now Step 3 (before Create Credentials), the "if prompted" misleading wording is gone, and the External-vs-Internal user-type choice is called out explicitly. P2-C.
  • New: OAuth Setup Guide Step 5 is split into Save + Authorize. The old single step packed three actions ("paste, save, authorize") into one line, and new users routinely missed the second click. Now Step 5 saves the credentials and Step 6 authorizes the connection. P2-D.
Bodholdt Backup for Google Drive v6.12.1
  • Fixed: "Could not calculate estimate" error on the dashboard. The pre-flight size estimator's "Core Root Files" section called `new DirectoryIterator($root)` against a never-defined variable, which threw a PHP fatal on every dashboard load and the Refresh Estimate button. Vestigial reference left over from the v6.10 `getSubPathname()` refactor. Defined `$root` from `ABSPATH` the same way the scope-dirs helper does. Single-line fix, no behavior change beyond the estimate now actually rendering. OneDrive was unaffected (its core-scope walk handles root files in the main iterator).
Bodholdt Backup for Google Drive v6.12.0
  • Fixed: Multisite retention manifest + retention setting were per-blog (architectural). The retention manifest (`bodholdt_gdrive_backup_ids`) and the configured backup-retention count (`bodholdt_gdrive_retention`) were stored as per-blog WordPress options, but the Google Drive folder they track is per-account — shared across every blog in the network. On multisite installs, a customer who activated the plugin from a non-main blog would hit a manifest scoped to that blog only; subsequent backup runs under a different blog context could see an empty manifest, skip the cap enforcement, and let the cloud grow silently past the configured retention. Both values are now stored as network-wide `site_option`s, mirroring the v6.8.0 license-storage migration. A one-time on-upgrade helper merges every existing blog's manifest into a single network manifest (de-duped by Drive file ID, so no data loss) and promotes the main blog's retention setting to network scope. Single-site installs are unaffected (`site_option` transparently falls back to `option` there). Plan §4.2 v6.12.
  • Fixed: Manifest seed could only run once per install (Bug C). The function that reconciles the manifest against the actual cloud folder was gated by `if ( ! empty( manifest_get() ) ) return;` — meaning seed only ran ONCE in the install's lifetime, the first time the manifest was empty. After that, no source of cloud↔manifest desync (manual operator file ops, pre-v6.1.1 backups inherited at upgrade time, etc.) could ever self-heal. Surfaced 2026-05-16 when Kyle's GDrive showed 9 files vs configured retention of 7 — 7 pre-v6.1.1 backups were already in the cloud at v6.1.1 install time, the first new backup populated the manifest, the seed never ran again, and the 7 pre-existing orphans were never caught. The function is now a reconciliation pass that runs every enforcer call. Healthy installs pay zero option writes per pass. Plan §0a 2026-05-16 evening.
  • Improved: HTTP-status logging in the manifest reconcile pass. Non-2xx Drive API responses are now logged to `error_log()` with the status code + body excerpt, matching the parity hardening already shipped on OneDrive in v5.11.1.
  • Improved: Uninstall now cleans up the network-scoped options + transients introduced in v6.8 (license keys) and v6.12 (manifest + retention).
Bodholdt Backup for Google Drive v6.11.0
  • New: Cross-engine collation portability. Backups taken on MariaDB 10.10+ (which defaults to the `utf8mb4_uca1400_*` collation family) now restore cleanly on MySQL 5.7 / 8.x and older MariaDB. The dump rewrites every `*_uca1400_*` collation to the closest portable equivalent (`utf8mb4_unicode_520_ci`, `utf8mb3_unicode_ci`, etc.) at emit time across CREATE TABLE / VIEW / TRIGGER / PROCEDURE / FUNCTION / EVENT statements. Set option `bodholdt_gdrive_translate_collations` to 0 if you need byte-for-byte fidelity (e.g., restoring back to the same MariaDB version).
  • New: Progress bar resumes on page reload. Refresh the WP admin tab while a backup is mid-run and the progress UI now picks up where it was — bar, status text, and poll loop all re-attach automatically from the server-side job-status transient. No more "did my backup die?" anxiety.
  • New: Default-exclusion patterns for dev artifacts. `apply-bodholdt-*-v*.py`, `__pycache__/`, `*.pyc`, and `*.source.php` are now excluded by default. Pairs with the v6.10.0 readability probe — the probe defends against batch-poisoning regardless, but pre-excluding keeps the manifest tidy and the zip leaner.
Bodholdt Backup for Google Drive v6.10.0
  • Fixed: Backup-engine drops Bodholdt-prefixed plugins/theme on symlinked-deploy patterns. Common deploy pattern (a separate clone of in-house plugins symlinked into `wp-content/plugins/`) used to silently drop those plugins from backup zips. Three independent root causes contributed; all three close in this release. Plan §4.5.6.
  • 1. The relpath calc used `getRealPath()` + `substr($path, strlen(ABSPATH))`, which on symlinked subtrees returned paths outside ABSPATH and produced corrupted relpath entries (e.g., `oldt/bodholdt-wordpress/plugins/...`). Replaced with `RecursiveIteratorIterator::getSubPathname()` (iteration-depth-tracked, symlink-safe) plus a per-scope `relpath_prefix` (`wp-content/plugins/` etc.). `bodholdt_gdrive_get_scope_dirs()` now returns each entry shaped as `[ 'path', 'relpath_prefix' ]` (back-compat-tolerant of older callers passing plain-string paths).
  • 2. PHP's `RecursiveDirectoryIterator::hasChildren()` defaults to `$allowLinks = false`, so the iterator emits symlinked DIR entries but never descends into them — the entire symlinked subtree gets skipped silently. Fixed via a small RDI subclass (`Bodholdt_Gdrive_FollowSymlink_RDI`) that overrides `hasChildren()` to pass `$allowLinks=true`.
  • 3. `ZipArchive::close()` silently drops the entire ~500-file staged batch when even one staged file is unreadable. Pre-validate readability with `@fopen($path, 'rb')` + `fclose()` before `addFile()`; unreadable files are skipped cleanly with optional `WP_DEBUG` logging. `addFile()` and `close()` return values are also checked + logged.
  • Fixed: VIEW emission order regression in multisite-network mode. v6.9.0 emitted each blog's views at the end of that blog's iteration, BEFORE the network-shared group ran. Views that JOIN `wp_users` (network-shared) failed at CREATE time on restore because wp_users hadn't been created yet. All non-table schema objects (views, triggers) are now deferred to the absolute tail of the dump, after every CREATE TABLE across every blog + the network-shared group. Plan §4.5.8.
  • New: Proper self-exclusion. The backup zip no longer includes the Bodholdt Backup for Google Drive plugin's own directory (Russian-doll prevention). Sibling Bodholdt plugins and the bodholdt-labs theme are deliberately INCLUDED so customers with multiple Bodholdt plugins get a complete backup. Self-exclusion uses canonical-relpath equality, not name-prefix substring matching.
  • New: Unreadable files are skipped cleanly instead of poisoning a 500-file batch. Previously, ZipArchive's deferred-read behavior meant a single 0600/0700 file could silently drop dozens of unrelated legitimate backups. The pre-`addFile` readability probe defends against this regardless of what's on disk.
Bodholdt Backup for Google Drive v6.9.0
  • New: Multisite-network backup mode (Free-tier-included). The backup engine now auto-detects single-site vs multisite and defaults to a complete-network backup on multisite installs. Network mode walks every blog via `switch_to_blog()`, dumps each blog's content tables, then dumps the network-shared tables (`wp_users`, `wp_usermeta`, `wp_blogs`, `wp_blogmeta`, `wp_site`, `wp_sitemeta`, `wp_registration_log`, `wp_signups`) exactly once. Restoring such a backup recreates the full multisite layout.
  • New: Per-blog mode (opt-in). Multisite admins who want per-customer subsite isolation can switch to per-blog mode in the network admin's Settings tab. Per-blog backups contain only that blog's content tables.
  • New: Backup manifest (`bodholdt-manifest.json`). Every zip now includes a manifest describing mode, plugin version, WP version, blog topology, tables dumped, and shared-table inclusion. The restore engine reads it first.
  • New: Restore-side mode compatibility check. Refuses incompatible mode combos (multisite backup → single-site target) to prevent orphan tables.
  • Single-site installs unchanged — no UI changes, no feature flag, no migration required.
Bodholdt Backup for Google Drive v6.8.0
  • New: Multisite-network compatible. License state (key, tier, status, last-seen-valid timestamp) is now stored as a network-wide site option. A customer who activates a Solo license on blog 1 of a multisite network has the license recognized automatically on every other blog. One-time migration runs on upgrade.
  • Single-site installs are unaffected.
Bodholdt Backup for Google Drive v6.7.0
  • New: Slack and Discord webhook notifications (Pro+). One URL field, auto-detected provider. Per-event toggles for backup success, backup failure, restore initiated, and watchdog stale-alert. Send-Test-Notification button in Settings.
  • New: Helper `bodholdt_gdrive_notifications_allowed()` returns true only for Pro/Bundle. Filterable for one-off overrides.
  • Failure notifications include the same auto-diagnosis hint as the email notification.
Bodholdt Backup for Google Drive v6.6.0
  • New: Free tier is now the default experience. No license required for full backup + restore at the Free cap (10 retained).
  • New: Tier badge inline in the plugin header (Free / Solo / Pro / Bundle).
  • New: Lapse banner — when a previously-active license becomes invalid, a dismissible banner reminds you to renew while backups continue at Free-tier limits.
  • New: Soft-degrade on lapse — backups keep running even with an invalid license. Retention auto-clamps to 10; selective restore disables.
  • Removed: Three hard-locks that previously blocked the admin UI, manual backups, and scheduled backups on non-valid licenses.
  • Setup wizard now also surfaces to Free users for OAuth-flow guidance.
Bodholdt Backup for Google Drive v6.4.0
  • New: Tier-gated selective restore. Pro and Bundle customers can choose what to restore (database, plugins, themes, uploads, core). Free + Solo restore everything.
  • New: Inline "Upgrade to Pro" hint shown next to disabled scope chooser on Free + Solo.
  • Server-side defense: restore handler coerces submitted scope to full on tiers without selective access.
Bodholdt Backup for Google Drive v6.3.0
  • New: Tier-aware retention cap helper. Free=10 / Solo=50 / Pro=100 / Bundle=100. Save handler clamps; settings input cap is dynamic.
  • New: Over-cap inline notice when a previously-saved retention value exceeds the current tier cap (e.g., after a downgrade).
  • New: Pre-flight `force_refresh()` before retention enforcement so tier downgrades take effect within one backup, not 12 hours later.
  • Licensing client now captures `tier_key` from server responses.
Bodholdt Backup for Google Drive v6.1.1
  • Critical fix: added backup-filename allowlist to retention enforcement. Previously the enforcer listed every child of the backup folder; user-dropped files in `Bodholdt Backups/<site>` could have been auto-deleted. Now the enforcer uses an ID-manifest written at upload time plus a filename regex check.
  • New: One-time seed migration on upgrade for existing backups.
Bodholdt Backup for Google Drive v6.1.0
  • New: Watchdog last-success timestamp with stale-alert.
  • New: Out-of-web-root staging directory.
Bodholdt Backup for Google Drive v6.0.0
  • New: Multipart-leak fix in email rendering.
Bodholdt Backup for Google Drive v5.12.0
  • Security hardening — strict base64 decoding, WP_DEBUG-gated error logs, Windows zip-path validation, OAuth state-transient race fix, path-traversal protection in uninstall, API response structure validation, hex-format validation on directory suffix.
  • GPL-2.0-or-later license file added. Languages directory added. CHANGELOG.md added.
Bodholdt Backup for Google Drive v5.11.0
  • Incremental backup mode. Selective backup scope. Setup wizard. Test email. Force unlock for stuck backup processes.
Bodholdt Backup for Google Drive v5.10.0
  • Selective restore. Sentinel-based SQL statement splitting. Pre-restore safety snapshot.
Bodholdt Backup for Google Drive v5.9.0
  • Sodium encryption support. HMAC-authenticated AES-256-CBC. Dark cyberpunk admin theme.
Bodholdt Backup for Google Drive v5.8.0
  • Resumable chunked upload to Google Drive. Backup retention policy with automatic cleanup. Email notifications.
Bodholdt Backup for Google Drive v5.7.0
  • Migration installer (standalone restore tool). Custom file/folder exclusions. Scheduled backups.
Bodholdt Backup for Google Drive v5.0.0
  • Initial release with Google Drive integration.
Bodholdt Backup for OneDrive v5.25.0
  • More cyan polish. "Start Backup Now" cloud icon is now pure white; the "Your Backups" table (column headers, file/Download/Restore icons, row hover) now uses the OneDrive blue instead of grey/purple; in-app "Upgrade to Pro" notices and the troubleshooting path now use the blue accent.
  • Logs toolbar. The "Apply" filter button is now a gradient button (Clear History was already red).
Bodholdt Backup for OneDrive v5.24.0
  • Admin polish. Dashboard "Success" and "Connected" status pills now use the OneDrive blue (matching the theme); "Calculate Size", "Test Connection", "Send Test Email", and "Send Test Notification" are now gradient buttons; and "Reset to Defaults" is now clearly marked red as a destructive action.
Bodholdt Backup for OneDrive v5.23.0
  • Distinct cyan admin + consistent buttons. The OneDrive admin is now explicitly themed in its signature cyan (matching its product page, so it reads as distinct from the green Google Drive admin), and the "Start Backup Now" button uses the same gradient as "Save Settings."
Bodholdt Backup for OneDrive v5.22.0
  • Admin styling now sourced from the shared Bodholdt Labs design system. The admin UI consumes the canonical `bod-admin.css` (the same dark-admin stylesheet shipped across Bodholdt Labs plugins) instead of a plugin-local copy, so the look stays consistent and future polish lands everywhere at once. Visually unchanged save for one accessibility fix (purple text colour bumped to a WCAG-AA contrast). No functional or behavioural change.
Bodholdt Backup for OneDrive v5.21.0
  • Pro edition with licensed auto-updates. This is the paid, off-directory build of Bodholdt Backup for OneDrive, distributed by Bodholdt Labs. It enables in-dashboard automatic updates delivered from the Bodholdt Labs licensing server when a valid license key is active (license key + site URL + product identifier only; never site content or backup data).
  • Selective restore available on every paid tier, including Solo — pick exactly which components (database, system files, themes, plugins, uploads) to restore. Retention is uncapped on all tiers (bounded only by a per-install sanity limit).
  • Slack/Discord webhook notifications for backup success/failure, restore-initiated, and stale-backup watchdog events (Pro and Bundle tiers).
  • Hardening (parity with the directory edition): first-party download streaming now uses the WordPress HTTP API; admin-notice scripts moved to `wp_add_inline_script`; staging/uninstall paths resolved via `wp_get_upload_dir()`; installer CSRF/lockfiles relocated to the system temp directory with a table-prefix validation guard; all request inputs run through `wp_unslash()` before sanitization.
  • Added an "External services" section to this readme documenting the Microsoft Graph / Microsoft OAuth endpoints, the Bodholdt Labs licensing/update server, and the WordPress.org secret-key (salt) request used during cross-server migration.
Bodholdt Backup for OneDrive v5.20.1
  • Fixed: footer line missing on installs that run third-party plugins which globally blank the WP admin footer. Some plugins (e.g. Taxonomy CSV Import/Export) register `__return_empty_string` against `admin_footer_text` at priority 11 across every admin page — which silently wiped our v5.20.0 footer line (Bodholdt Backup for OneDrive v5.X.X · Documentation · Support). Our filter now runs at priority 99, so it executes last and the footer renders as designed regardless of any other plugin's behavior.
Bodholdt Backup for OneDrive v5.20.0
  • UI: version moved out of the page title. The big "BODHOLDT BACKUP FOR ONEDRIVE V5.X.X" plugin-page heading is now just the product name + the tier badge ("FREE" / "Pro" / etc.). The version still appears, but now in the WordPress admin footer alongside Documentation and Support links — discreet and out of the way. Aligned with how WordPress core, WooCommerce, Yoast, and most established plugins handle this.
Bodholdt Backup for OneDrive v5.19.2
  • Brand consistency follow-up. Two leftover short-form "Bodholdt OneDrive" labels weren't updated during the v5.19.0 rebrand: the WP admin sidebar menu entry, and the License sub-page heading. Both now read "Bodholdt Backup for OneDrive" to match the rest of the plugin. No behavior change.
Bodholdt Backup for OneDrive v5.19.1
  • Fixed: Setup Wizard never appeared on fresh installs. A function-scope bug in the dashboard renderer left the wizard's display gate (`$show_wizard`) undefined, which PHP 8+ surfaces as a Warning and silently coerces to null — so on a fresh install the wizard quietly didn't render, leaving new users with no guided onboarding (just an empty dashboard asking them to enter OAuth credentials with no setup hand-holding). The wizard now renders correctly as designed.
Bodholdt Backup for OneDrive v5.19.0
  • Renamed to "Bodholdt Backup for OneDrive." Adopts the trademark-safe "for [destination]" naming pattern across all customer-facing surfaces (plugin header, admin menus, dashboard, email subjects, logs). Functionally identical — no settings or backups affected.
  • Setup wizard — improved "Testing on a local site?" callout. The advisory now recommends Local's "Live Link" feature (HTTPS public URL) as a fallback if Microsoft still rejects a `.local` redirect URI after enabling HTTPS, so testers have a clear path forward.
  • Tested up to: WordPress 7.0. Verified compatibility with the current stable release.
  • Honest claims. Softened a few storefront descriptions to be evergreen rather than time-specific ("accessibility-minded" rather than a literal compliance grade; "security-audited each release" rather than a specific score) so they stay accurate without periodic updates.
  • wp.org Contributors handle updated to `bodholdtlabs` in preparation for wp.org plugin directory submission.
Bodholdt Backup for OneDrive v5.18.0
  • Per-site backup folders. Each site now backs up to its own uniquely-named folder in your OneDrive (identified by a per-site ID), so multiple WordPress sites sharing one Microsoft account no longer write into the same folder. If a shared folder from an earlier version is detected, the plugin moves this site to its own folder and shows a one-time notice — nothing is deleted and your existing backups stay untouched.
Bodholdt Backup for OneDrive v5.17.2
  • Licensing fix + brand consolidation. License activation, validation, and auto-updates now point at the correct server (bodholdtlabs.com), so license keys validate reliably (the previous host redirected and dropped the request). Author byline, support email, and account links updated to Bodholdt Labs / bodholdtlabs.com.
Bodholdt Backup for OneDrive v5.17.1
  • Retention tier fix. Solo and Agency licenses now get their correct backup-retention limits (Solo 50, Agency unlimited up to the system maximum), and Agency now includes selective restore and Slack/Discord notifications. Previously these tiers fell back to the Free 10-backup limit because the entry tier is issued as "default" and the top tier as "agency" — both are now recognized. Pro and Bundle are unchanged.
Bodholdt Backup for OneDrive v5.17.0
  • Fun + accessibility pass. Small delight and polish touches, matched to the Bodholdt Backup for Google Drive sibling.
  • New: A running "backups protected" tally. The Backup Health card now shows how many backups you've made and the total size kept safe (e.g. "🛡️ 47 backups protected · 182 GB kept safe"). This counts off dedicated counters, so clearing your on-screen log history doesn't reset it.
  • New: First-backup moment. Your very first successful backup now gets a bigger one-time celebration and a "Your first backup is done — your site is officially protected!" message.
  • New: Restore celebration. Finishing a restore now greets you with a gentle celebration and a "Site restored — welcome back." message.
  • Accessibility: Celebrations respect "reduced motion." All confetti animations now honor your operating system's "reduce motion" setting (the JS canvas confetti previously ignored it).
  • Clearer destructive confirms. The "Disconnect OneDrive" and "Delete all backup log history" confirmations now spell out exactly what happens and what is NOT affected (your cloud backups are never touched).
  • Warmer copy on the empty "Your Backups" state and the backup-complete message.
Bodholdt Backup for OneDrive v5.16.1
  • Cross-plugin parity follow-ups.
  • "Reset to Defaults" now restores a sensible Daily 3:00 AM backup (was previously "Manual only"; both backup plugins now reset to the same default). And a scheduled backup on a site that hasn't connected OneDrive yet now skips quietly, instead of emailing a daily "backup failed" alert.
  • Internal consistency: the default backup schedule is now Daily for fresh installs (matching Bodholdt Backup for Google Drive). The one-click backup flow is unchanged.
Bodholdt Backup for OneDrive v5.16.0
  • Steve Jobs / Grandma Test Pass — Rounds 2-5 (error states, settings parity, micro-interactions, visual polish). Brings the plugin to full feature/UX parity with Bodholdt Backup for Google Drive. Bundles three internal batches:
  • *Error handling (R2):* clearer test-connection and restore-failure messages; the failed-backup notice now links straight to Settings and Logs; "Disconnect" now tells you how to fully revoke this site's access in your Microsoft account; a mistyped notification email is no longer saved silently; cancelling at Microsoft's consent screen is handled gracefully; backup-download errors are now friendly with a back link.
  • *Settings (R3):* added an Hourly-schedule performance warning, hid the time picker when scheduling is set to Manual, added a server-side "Settings saved" confirmation, and clearer "Backups to Keep" / "Migration Tools" / file-exclusion copy.
  • *Polish (R4/R5):* reliable copy-to-clipboard (with fallback), a proper "Refreshing…" working state on the size estimate, and all admin brand colors moved onto the shared design-token system (so the warning color is now consistent across both backup plugins). Build pipeline excludes editor/OS cruft from the distributed zip.
Bodholdt Backup for OneDrive v5.15.0
  • Fixed: Staging directory now has a graceful fallback when `/var/lib/bodholdt-staging/` isn't writable. Pre-v5.15.0 the plugin defaulted `BODHOLDT_OD_DIR` to `/var/lib/bodholdt-staging/onedrive/<random-suffix>/` with no fallback — a path that only exists on operator-prepared hosts. On any host without root access (most managed WordPress hosts, every local development environment, every WP.org reviewer install), the `mkdir()` call returned false, the next `fopen()` failed silently, and the backup or restore died mid-flight with the AJAX progress indicator simply disappearing at "Fetching Download link…" — no error to the user. v5.15.0 adds a three-tier fallback hierarchy resolved at plugin-load time: (1) `/var/lib/bodholdt-staging/onedrive/<suffix>/` if writable (preserves the post-2026-05-05 security model on operator-prepared hosts), (2) `sys_get_temp_dir() . '/bodholdt-staging-onedrive/<suffix>/'` if `/var/lib/` isn't writable (system temp, ephemeral but still isolated from the web document root), (3) `wp-content/uploads/.bodholdt-staging-onedrive/<suffix>/` as a last resort with three hardening layers (an `.htaccess` deny-all rule for Apache, an `index.php` returning HTTP 403 as defense-in-depth, and a docs-recommended nginx `location` deny rule in the FAQ). Each fallback transition is logged via `error_log()` so operators on hardened hosts can see why the plugin moved off the primary. Override via `define('BODHOLDT_OD_DIR', __DIR__ . '/your/custom/path/')` in wp-config.php to bypass the resolution entirely. Symmetric fix to the Bodholdt Backup for Google Drive plugin's v6.15.0 change (also shipped today). Plan §4.5.13.
Bodholdt Backup for OneDrive v5.14.2
  • Fixed: Restore engine was silently dropping `wp-content/mu-plugins/`, `wp-content/languages/`, and WordPress drop-ins (object-cache.php / advanced-cache.php / db.php / maintenance.php). OneDrive's backup engine already picked up mu-plugins via the wider `core` scope walk (no v5.14.1 backup-side hotfix needed), but the restore engine only copied `themes/`, `plugins/`, and `uploads/` from the extracted backup to the destination's `wp-content/`. Result: any disaster-recovery restore landed a site without its must-use plugins (custom auth handlers, debug-log filters, multisite-shared utilities, etc.) and without its drop-ins (Redis object cache, full-page cache, custom DB layer, maintenance-mode override). The restore engine now treats `mu-plugins/` and `languages/` like the database — always copied, no user-facing scope toggle — and copies the four standard wp-content drop-ins when they're present in the backup. Surfaced 2026-05-17 night during the §4.5.10 GDrive end-to-end retest, applied symmetrically to OneDrive. Plan §4.5.15. (v5.14.1 was never shipped — version number reserved as the GDrive companion to this fix's mirror; v5.14.2 is the first shipped restore-side fix on the OneDrive side.)
  • Fixed: Restore engine no longer unconditionally drops your other Bodholdt backup plugin. Prior versions hard-skipped both `bodholdt-onedrive-backup` AND `bodholdt-google-drive-backup` directories during restore. The self-skip is necessary (a plugin can't safely overwrite itself mid-execution), but the sibling-skip meant customers running both plugins as belt-and-suspenders backup would lose one on every restore. The restore engine now skips the sibling only when it's currently network-active (i.e. running and could collide); installed-but-inactive siblings are restored normally. Plan §4.5.18.
Bodholdt Backup for OneDrive v5.14.0
  • New: Unified 4-step setup wizard. The setup wizard has been rebuilt to a 4-step shape that mirrors the Bodholdt Backup for Google Drive wizard exactly: Welcome → Connect OneDrive (credentials inline, with a collapsible "How do I get these?" guide) → Schedule → Run Your First Backup with size estimate. The previous 3-step wizard ended on a "go elsewhere" link to the Settings tab — new users routinely got lost there. The new wizard keeps everything in one flow, with the full 5-step OAuth setup guide reachable from the credentials step via a `<details>` panel. State persists across the OAuth round-trip (a new `bodholdt_od_wizard_step` option), so refreshing the page or returning from Microsoft mid-flow picks up where the user left off. Closes Steve Jobs pass Round 1. P1-C.
  • New: Wizard returns to the dashboard after OAuth. Previously OneDrive's OAuth success redirected to the Settings tab, which meant the user had to find their way back to the dashboard before the wizard could continue. Now, when a user is mid-wizard, the OAuth callback redirects directly to the dashboard tab so the wizard resumes at the Schedule step automatically.
  • New: New AJAX endpoint `bodholdt_od_wizard_save_creds`. Saves Client ID + Client Secret from the wizard's Step 2 and returns the Microsoft authorization URL with a CSRF state nonce. Byte-for-byte compatible with the existing "Authorize OneDrive Access" button on the Settings tab.
Bodholdt Backup for OneDrive v5.13.0
  • New: "Set up" link on the Plugins page. After activating, a "Set up" / "Backups" quick-link now appears next to Activate/Deactivate in the WordPress Plugins list, giving you one-click access to the dashboard. Standard WP pattern; we should have shipped this in v3. Steve Jobs pass Round 1, P1-A.
  • New: Friendlier setup-guide language. Wizard welcome copy and the OAuth Setup Guide on Settings have been rewritten to drop "Microsoft Azure app" / "app registration" jargon in favor of consumer-friendly framing ("free Microsoft developer account", "Microsoft Developer Portal"). Technical terms ("Client ID", "Client Secret", "Redirect URI") stay where they're necessary, but the welcome copy no longer reads like enterprise IT. P1-D.
  • New: OAuth Setup Guide Step 5 is split into Save + Authorize. The old single Step 5 packed three actions ("copy, paste+save, authorize") into one line, and new users routinely missed the second click. Now Step 5 saves the credentials and Step 6 authorizes the connection. P2-D.
  • New: Backup destination renders as a breadcrumb. The Settings tab's "Backup Destination" line now shows `Your OneDrive → Apps → Bodholdt Backup → <site>` instead of the raw `/Apps/Bodholdt Backup/<site>/` path. Easier for non-developers to map to their OneDrive web UI. P2-F.
  • New: Backup Size Estimate auto-loads on dashboard. Mirror of GDrive's behavior — the estimate card now populates on dashboard visit instead of waiting for a manual "Calculate Size" click. P2-E.
Bodholdt Backup for OneDrive v5.12.0
  • Fixed: Multisite retention manifest + retention setting were per-blog (architectural). The retention manifest (`bodholdt_od_backup_ids`) and the configured backup-retention count (`bodholdt_od_retention`) were stored as per-blog WordPress options, but the OneDrive folder they track is per-account — shared across every blog in the network. On multisite installs, a customer who activated the plugin from a non-main blog would hit a manifest scoped to that blog only; subsequent backup runs under a different blog context (e.g., from network admin, or scheduled cron under blog 1) saw an empty manifest, skipped the cap enforcement, and let the cloud grow silently past the configured retention. Both values are now stored as network-wide `site_option`s, mirroring the v5.8.0 license-storage migration. A one-time on-upgrade helper merges every existing blog's manifest into a single network manifest (de-duped by Graph item ID, so no data loss) and promotes the main blog's retention setting to network scope. Customer impact: multisite users will see retention enforce correctly on the next scheduled backup; single-site users are unaffected (`site_option` transparently falls back to `option` there). Plan §4.2 v5.12.
  • Fixed: Manifest seed could only run once per install (Bug C). The function that reconciles the manifest against the actual cloud folder was gated by `if ( ! empty( manifest_get() ) ) return;` — meaning seed only ran ONCE in the install's lifetime, the first time the manifest was empty. After that, no source of cloud↔manifest desync (lookup_item_id silent skip after upload, manual operator file ops, pre-v5.1.1 backups inherited at upgrade time, etc.) could ever self-heal. The function is now a reconciliation pass that runs every enforcer call — it lists the folder once, diffs against the known manifest, and appends only previously-unknown canonical-named cloud files. Healthy installs pay zero option writes per pass; leaky installs self-recover on the next backup. Plan §0a 2026-05-16 evening.
  • Improved: Uninstall now cleans up the network-scoped options + transients introduced in v5.8 (license keys) and v5.12 (manifest + retention). Previously these were left behind on plugin uninstall on multisite, harmless but untidy.
Bodholdt Backup for OneDrive v5.11.1
  • Fixed: Retention seed-migration silently broken since v5.1.1. The `bodholdt_od_manifest_seed_from_folder()` function used `$orderby=createdDateTime` in its Microsoft Graph query, which Graph rejects on the `:/children` endpoint with HTTP 400 "Operation not supported." The function silently fell through and returned with an empty manifest, so installs with desynced manifests (caused by occasional `lookup_item_id` failures after upload) never self-recovered, and cloud backup counts could grow unbounded past the user-configured retention. Removed the unsupported `$orderby` parameter; the enforcer already sorts the manifest in PHP. Customer impact: customers whose backup count had drifted above their configured retention will see the cap re-enforce on the next scheduled backup run.
  • New: HTTP-status logging in the manifest seed. Non-2xx Graph responses (token expired, throttled, unsupported parameter, etc.) are now logged to `error_log()` so future Graph-side regressions surface in WP_DEBUG instead of vanishing silently.
Bodholdt Backup for OneDrive v5.11.0
  • New: Cross-engine collation portability. Backups taken on MariaDB 10.10+ (which defaults to the `utf8mb4_uca1400_*` collation family) now restore cleanly on MySQL 5.7 / 8.x and older MariaDB. The dump rewrites every `*_uca1400_*` collation to the closest portable equivalent (`utf8mb4_unicode_520_ci`, `utf8mb3_unicode_ci`, etc.) at emit time across CREATE TABLE / VIEW / TRIGGER / PROCEDURE / FUNCTION / EVENT statements. Set option `bodholdt_od_translate_collations` to 0 if you need byte-for-byte fidelity (e.g., restoring back to the same MariaDB version).
  • New: Progress bar resumes on page reload. Refresh the WP admin tab while a backup is mid-run and the progress UI now picks up where it was — bar, status text, and poll loop all re-attach automatically from the server-side job-status transient. No more "did my backup die?" anxiety.
  • New: Default-exclusion patterns for dev artifacts. `apply-bodholdt-*-v*.py`, `__pycache__/`, `*.pyc`, and `*.source.php` are now excluded by default. Pairs with the v5.10.0 readability probe — the probe defends against batch-poisoning regardless, but pre-excluding keeps the manifest tidy and the zip leaner.
Bodholdt Backup for OneDrive v5.10.0
  • Fixed: Backup-engine drops Bodholdt-prefixed plugins/theme on symlinked-deploy patterns. Common deploy pattern (a separate clone of in-house plugins symlinked into `wp-content/plugins/`) used to silently drop those plugins from backup zips. Three independent root causes contributed; all three close in this release. Plan §4.5.6.
  • 1. The relpath calc used `getRealPath()` + `substr($path, strlen(ABSPATH))`, which on symlinked subtrees returned paths outside ABSPATH and produced corrupted relpath entries (e.g., `oldt/bodholdt-wordpress/plugins/...`). Replaced with `RecursiveIteratorIterator::getSubPathname()` (iteration-depth-tracked, symlink-safe) plus a per-scope `relpath_prefix` (`wp-content/plugins/` etc.).
  • 2. PHP's `RecursiveDirectoryIterator::hasChildren()` defaults to `$allowLinks = false`, so the iterator emits symlinked DIR entries but never descends into them — the entire symlinked subtree gets skipped silently. Fixed via a small RDI subclass (`Bodholdt_OD_FollowSymlink_RDI`) that overrides `hasChildren()` to pass `$allowLinks=true`. File-level symlinks (e.g. mu-plugins) were always picked up correctly because `isDir()` returns false for them.
  • 3. `ZipArchive::close()` silently drops the entire ~500-file staged batch when even one staged file is unreadable (e.g., owner-only 0600 perms; `close()` returns false with a `Permission denied` warning, but PHP discards every legitimate sibling file in the batch alongside the bad one). Pre-validate readability with `@fopen($path, 'rb')` + `fclose()` before `addFile()`; unreadable files are skipped cleanly with optional `WP_DEBUG` logging. `addFile()` and `close()` return values are also checked + logged.
  • Fixed: File-collector path corruption for files reached through symlinks. Same root cause as the symlink-relpath bug above. Files reached via a symlinked subtree (e.g., `wp-content/mu-plugins/bodholdt-*.php` on the same deploy pattern) landed in the zip at `oldt/bodholdt-wordpress/mu-plugins/` instead of the canonical `wp-content/mu-plugins/`. The `getSubPathname()` approach produces correct canonical paths regardless of symlink chains. Plan §4.5.7.
  • Fixed: VIEW emission order regression in multisite-network mode. v5.9.0 emitted each blog's views at the end of that blog's iteration, BEFORE the network-shared group ran. Views that JOIN `wp_users` (network-shared) failed at CREATE time on restore because wp_users hadn't been created yet. All non-table schema objects (views, triggers) are now deferred to the absolute tail of the dump, after every CREATE TABLE across every blog + the network-shared group. Plan §4.5.8.
  • New: Proper self-exclusion. The backup zip no longer includes the Bodholdt Backup for OneDrive plugin's own directory (Russian-doll prevention). Sibling Bodholdt plugins and the bodholdt-labs theme are deliberately INCLUDED so customers with multiple Bodholdt plugins get a complete backup. Self-exclusion uses canonical-relpath equality, not name-prefix substring matching.
  • New: mu-plugins and other non-scoped wp-content children are now backed up. Previously the "core" scan-and-skip rule excluded everything under wp-content. Relaxed so only the three scope-mapped subtrees (`wp-content/plugins/`, `wp-content/themes/`, `wp-content/uploads/`) are skipped during the core scan; `wp-content/mu-plugins/`, `wp-content/languages/`, etc. now land in the backup at their canonical paths and restore to where they belong.
  • New: Unreadable files are skipped cleanly instead of poisoning a 500-file batch. Previously, ZipArchive's deferred-read behavior meant a single 0600/0700 file (e.g., a forgotten dev artifact) could silently drop dozens of unrelated legitimate backups. The pre-`addFile` readability probe defends against this regardless of what's on disk.
Bodholdt Backup for OneDrive v5.9.0
  • New: Multisite-network backup mode (Free-tier-included). The backup engine now auto-detects single-site vs multisite and defaults to a complete-network backup on multisite installs. Network mode walks every blog via `switch_to_blog()`, dumps each blog's content tables, then dumps the network-shared tables (`wp_users`, `wp_usermeta`, `wp_blogs`, `wp_blogmeta`, `wp_site`, `wp_sitemeta`, `wp_registration_log`, `wp_signups`) exactly once. Restoring such a backup recreates the full multisite layout.
  • New: Per-blog mode (opt-in). Multisite admins who want per-customer subsite isolation can switch to per-blog mode in the network admin's Settings tab. Per-blog backups contain only that blog's content tables — no shared tables, no other blogs.
  • New: Backup manifest (`bodholdt-manifest.json`). Every zip now includes a manifest at the zip root describing mode, plugin version, WP version, blog topology, tables dumped, and shared-table inclusion. The restore engine reads it first to verify compatibility.
  • New: Restore-side mode compatibility check. The engine refuses to restore a multisite backup onto a single-site install (the wp_N_* tables don't belong) and warns when a single-site backup is being restored onto a multisite (data lands on current blog only).
  • Single-site installs continue to behave exactly as before — no UI changes, no feature flag, no migration required.
  • Database export entry function now accepts an optional `$mode` parameter for callers that want to force a specific mode regardless of saved settings.
Bodholdt Backup for OneDrive v5.8.0
  • New: Multisite-network compatible. License state (key, tier, status, last-seen-valid timestamp) is now stored as a network-wide site option. A customer who activates a Solo license on blog 1 of a multisite network has the license recognized automatically on every other blog. One-time migration runs on upgrade — existing per-blog license data is copied to network scope.
  • Single-site installs are unaffected (site_option falls back to option there).
Bodholdt Backup for OneDrive v5.7.0
  • New: Slack and Discord webhook notifications (Pro+). One URL field, auto-detected provider. Per-event toggles for backup success, backup failure, restore initiated, and watchdog stale-alert. Send-Test-Notification button in Settings.
  • New: Helper `bodholdt_od_notifications_allowed()` returns true only for Pro/Bundle. Filterable via `apply_filters( 'bodholdt_od_notifications_allowed', $allowed, $tier )` for one-off overrides.
  • Failure notifications include the same auto-diagnosis hint as the email notification (token / quota / disk / network).
Bodholdt Backup for OneDrive v5.6.0
  • New: Free tier is now the default experience. No license required for full backup + restore at the Free cap (10 retained).
  • New: Tier badge inline in the plugin header (Free / Solo / Pro / Bundle).
  • New: Lapse banner — when a previously-active license becomes invalid, a dismissible banner reminds you to renew while backups continue at Free-tier limits.
  • New: Soft-degrade on lapse — backups keep running even with an invalid license. Retention auto-clamps to 10; selective restore disables. Manual + scheduled backups continue.
  • Removed: Three hard-locks that previously blocked the admin UI, manual backups, and scheduled backups on non-valid licenses.
Bodholdt Backup for OneDrive v5.4.0
  • New: Tier-gated selective restore. Pro and Bundle customers can choose what to restore (database, plugins, themes, uploads). Free + Solo restore everything.
  • New: Inline "Upgrade to Pro" hint shown next to disabled scope chooser on Free + Solo.
  • Server-side defense: restore handler coerces submitted scope to full on tiers without selective access.
Bodholdt Backup for OneDrive v5.3.0
  • New: Tier-aware retention cap helper (`bodholdt_od_retention_cap`). Free=10 / Solo=50 / Pro=100 / Bundle=100. Save handler clamps; settings input cap is dynamic.
  • New: Over-cap inline notice when a previously-saved retention value exceeds the current tier cap (e.g., after a downgrade).
  • New: Pre-flight `force_refresh()` before retention enforcement so tier downgrades take effect within one backup, not 12 hours later.
  • Licensing client now captures `tier_key` from server responses.
Bodholdt Backup for OneDrive v5.1.1
  • Fixed: Rename-bypass in the retention enforcer. Previous folder-listing + regex enforcer let a renamed backup file slip past the cap entirely. Replaced with an ID-manifest written at upload time — renames can no longer bypass retention.
  • New: One-time seed migration on upgrade.
Bodholdt Backup for OneDrive v5.1.0
  • Last-success-timestamp watchdog with stale-alert.
  • Out-of-web-root staging directory (fixes a 2026-05 SQL-dump exposure risk).
Bodholdt Backup for OneDrive v5.0.0
  • Retention pagination + ordering fix — folders with more than 200 backups now prune correctly.
Bodholdt Backup for OneDrive v4.14.1
  • Settings save reliability fix.
Bodholdt Backup for OneDrive v4.14.0
  • Email-delivery failure detection. Plain-text email fallback. Migration key now included in success emails.
Bodholdt Backup for OneDrive v4.0.0
  • Setup wizard, selective restore, pre-restore snapshot, ZIP integrity verification, atomic locking, contextual help tabs.
Bodholdt Backup for OneDrive v3.0.0
  • Initial Bodholdt-Engine rewrite. OAuth 2.0. Resumable Graph API uploads. AES-256-CBC + Sodium encryption. Migration installer.
Bodholdt Contact v4.2.0
  • White-label appearance. The widget now offers Light, Dark, or Auto (match the visitor's system) display modes plus a corner-style choice, all derived from your single accent color with readable text and contrast shades computed automatically — so it blends into any site. The settings page got a matching refresh and the live preview reflects every change.
Bodholdt Licensing v10.24.8
  • Admin polish: removed an off-center icon from the Generate Code button so the call-to-action reads cleanly.
Bodholdt Licensing v10.24.7
  • Admin polish: clearer button colours — store/positive actions are green, reversible "Revoke" is amber, permanent "Delete" is red, with a consistent focus ring.
Bodholdt Licensing v10.24.6
  • Admin polish: unified button styling across every admin screen so secondary buttons stay readable on the dark theme.
Bodholdt Licensing v10.24.5
  • Admin polish: the license-expiry Save button now stays beside the date field, and the License Database table no longer collides its columns on narrower screens.
Bodholdt Licensing v10.24.4
  • Admin polish: fixed the Appearance live-preview in light mode, made outline buttons readable, and tidied the Issue New License form alignment and License Database table layout.
Bodholdt Licensing v10.24.3
  • Admin polish: brightened hard-to-read dark-blue/purple text throughout the admin for better contrast on the dark theme.
Bodholdt Licensing v10.24.2
  • Admin polish: more contrast fixes on the Emails and License Database screens.
Bodholdt Licensing v10.24.1
  • Admin polish: contrast fixes across the setup checklist, dashboard, forms, and data tables on the dark theme.
Bodholdt Licensing v10.24.0
  • White-label customer checkout. Your customer-facing checkout, portal, and pricing now follow a single brand colour with neutral defaults — no Bodholdt branding imposed. A new Settings → Appearance tab lets you set the brand colour, light/dark/auto mode, corner style, and font, with a live preview of the real checkout components.
Bodholdt Licensing v10.23.11
  • Admin redesign QA: fixed an empty status chip on collapsed Products rows.
Bodholdt Licensing v10.23.10
  • Admin redesign QA: Products rows now show the status chip only when collapsed and the footer only when expanded.
Bodholdt Licensing v10.23.9
  • Admin redesign — Products. The Products screen now renders as tidy collapsible cards with a status chip, taming the densest admin page.
Bodholdt Licensing v10.23.8
  • Admin redesign — Reports. Charts, date pickers, and CSV links restyled for the dark theme and full legibility.
Bodholdt Licensing v10.23.7
  • Admin redesign — Activity Log. Action labels and the empty state restyled for readability.
Bodholdt Licensing v10.23.6
  • Admin redesign — Manage Licenses. Headers, filters, status pills, and row details restyled for the dark theme.
Bodholdt Licensing v10.23.5
  • Admin redesign — Dashboard. Stat cards, the earnings hero, and the setup checklist restyled for the dark theme.
Bodholdt Licensing v10.23.4
  • Admin redesign — Getting Started. Welcome, steps, and progress made legible on the dark theme.
Bodholdt Licensing v10.23.3
  • Admin redesign — Settings. All Settings fields and info boxes restyled for the dark theme.
Bodholdt Licensing v10.23.2
  • Admin redesign — Plugin Integration. Code blocks, the test-SDK panel, and steps restyled with clear branding.
Bodholdt Licensing v10.23.1
  • Admin redesign foundation (cont.). The main card sections, settings sub-tabs, and primary buttons adopted the new dark theme.
Bodholdt Licensing v10.23.0
  • Refreshed admin design. The licensing admin now uses the Bodholdt Labs dark design system — a new branded header and tabs, and a consistent dark canvas across every screen. Visual only; behaviour and settings are unchanged.
Bodholdt Contact v4.1.3
  • WordPress.org readiness pass: added translator comments for placeholder strings, removed the manual text-domain load (WordPress loads translations automatically), and hardened a server-variable read. No change to how the widget behaves.
Bodholdt Licensing v10.22.6
  • Support routing. Help/FAQ calls-to-action now point customers to the Support page to open a ticket, instead of emailing — so pre-sales and support requests are always captured.
Bodholdt Licensing v10.22.5
  • Fix: bundles no longer show a "Free Trial" option at checkout, and a product with no trial configured no longer defaults to a 14-day trial — a missing trial length now correctly means no trial.
Bodholdt Licensing v10.22.4
  • Customers can manage their own sites. The "My Keys" view in the customer portal now lists each license's active sites (used / allowed) with a Remove button, so a customer who moves to a new domain or shuts a site down can free a slot themselves — no support email needed. The site limit itself is unchanged; this just lets customers swap which sites use it.
Bodholdt Licensing v10.22.3
  • Faster product releases. Upload (or drag-and-drop) a plugin ZIP on the Products screen and the Version, Tested up to, Requires WP/PHP, and latest changelog now auto-fill straight from the plugin — review and Save to publish. No more retyping the version after every build.
Bodholdt Licensing v10.22.2
  • Feature-comparison tables. Each product page now shows an at-a-glance feature comparison across its tiers, and the Buy Plugins page shows one per product line. All tables read from a single source so they stay in step with the pricing cards.
Bodholdt Licensing v10.22.1
  • Changelog readability. Tidied the public changelog so it reads cleanly for customers — removed internal planning and process references. No functional changes.
Bodholdt Contact v4.1.2
  • Renamed the Bodholdt Tickets integration to its current name and refined the verification step layout — the email address and the "enter your code" instruction now sit on their own lines.
Bodholdt Licensing v10.22.0
  • Customer-portal integration hooks. Two new extension points let companion plugins (such as Bodholdt Ticketing) surface their own content inside your "My Keys" customer portal: `cls_portal_verified_customer_html` (append HTML to a verified customer's result) and `cls_portal_option_cards` (add a card to the portal option grid). Both are additive and no-op when nothing is hooked, so this release changes nothing on its own.
Bodholdt Licensing v10.21.2
  • Checkout cart polish. The checkout button's loading spinner now shows only during the in-flight request (it previously spun the whole time), and long product names no longer get truncated in the cart's product picker — the field uses the plugin font with an ellipsis and the cart row was widened and rebalanced.
Bodholdt Licensing v10.21.1
  • Success-page button contrast. Forced the success-page button labels to a visible color so a host theme's link color can't render them blue-on-blue.
Bodholdt Contact v4.1.1
  • Settings page now shows a live preview of the widget that updates as you change the accent color, header, and intro — §7.4 Steve Jobs pass.
Bodholdt Tickets v0.18.1
  • Brand consolidation: author byline + the License page's support email now use Bodholdt Labs / bodholdtlabs.com.
Bodholdt Tickets v0.18.0
  • Licensing + plans. Bodholdt Tickets is now a licensed product with a Free tier and paid Solo/Studio/Foundry plans. Enter your license key under Tickets → License to unlock your plan and receive automatic updates. The Free tier includes 1 agent and 25 tickets/month across email, the web form, and the customer portal. Solo adds the AI co-pilot, the embeddable cross-site widget, categories, gamification, and aggregate reports with CSV. Studio adds departments & routing plus the per-agent and per-department report breakdowns. Foundry removes all limits. Your desk keeps working if a license lapses — it falls back to Free-tier limits after a short grace period, never losing your data. (No database changes.)
Bodholdt Licensing v10.21.0
  • Plan tiers are now real. Your plan (Hobby / Studio / Foundry) is now recognized by the plugin and enforced: Reports & analytics is a Studio+ feature (Hobby sees an upgrade prompt), and Hobby is limited to one product. Your tier is read securely from your license at validation time — and if your tier can't be determined, you keep full access (no one is ever locked out of their own data). Also fixes the underlying signal so add-on plugins (e.g. the backup plugins) correctly receive their tier. Adds a `tier` column to the license table, applied automatically on update.
Bodholdt Licensing v10.20.0
  • Reports & analytics. A new Reports page (under the Bodholdt Licensing menu) turns the data you already have into operator insight. Revenue: MRR, ARR run-rate, ARPU, active subscriptions, refund rate, a gross-revenue trend, and MRR broken down by product — read live from Stripe (read-only), cached for an hour with a one-click Refresh. Licenses: active/expiring/lifetime counts and new-in-range, status mix, seat utilization with an "at limit" upsell signal, top products, and trial→paid conversion — all from your license table. Pick a date range (7/30/90 days, this month, or custom) and export any section to CSV. Revenue needs your Stripe key connected; the license reports work without it.
Bodholdt Contact v4.1.0
  • Bodholdt Tickets front-door. When the Bodholdt Tickets plugin is active, contact submissions now create a support ticket (and notify you by email) instead of a plain email — so customers can track and reply to them, and you work them in the Support Desk. If Tickets has categories, the widget shows a “What’s this about?” picker. Without Tickets, it behaves exactly as before (emails you). All recognition + anti-spam unchanged.
Bodholdt Contact v4.0.0
  • Reconfigured for distribution: renamed to Bodholdt Contact, multi-tenant settings page (brand name, accent color, copy, notification email, customer-recognition mode), all branding de-hardcoded, internationalized (text domain `bodholdt-contact`), and packaged for release. Backward-compatible class alias for the legacy name. All anti-spam protections (one-time code, honeypot, rate limiting, brute-force lockout, timing-safe comparison) retained.
Bodholdt Tickets v0.17.0
  • Reporting & analytics. A new supervisor-only Reports view in the Support Desk surfaces what the desk already tracks: volume over time (created vs resolved + open backlog, sliced by channel, category, and department), speed & SLA (first-response and resolution times with median/avg/p90 and % within a target you choose), CSAT (average, the 1–5 spread, a daily trend, and breakdowns by agent and department), per-agent workload & outcomes, and the category/channel/tag mix. Pick a date range (7/30/90 days, this month, or custom) and export any report to CSV. Agents can opt out of the peer leaderboard from their stats panel (supervisors still see everyone in reports). Computed live from your data — no setup required. (Adds reporting indexes — applied automatically on update.)
Bodholdt Tickets v0.16.0
  • Departments & routing. Group your agents into departments (Sales, Support, Billing…), then map each category to a department so incoming tickets auto-route to the right team — set it all up under Tickets → Departments & routing, and tick which departments each agent belongs to. In the Support Desk, the department shows on every ticket, you can filter the queue by department (or “My departments”), and re-route a ticket by hand from the ticket header. Built to run real multi-team support out of the box; solo operators can simply leave it empty. (Adds an `assigned_group` column — applied automatically on update.)
Bodholdt Tickets v0.15.0
  • Categories. Define your own ticket categories (e.g. one per product, or Billing / General) under Ticketing settings; customers then pick “What’s this about?” on the front-end form and the embeddable widget, and you can filter the Support Desk queue by category. The category shows on the ticket and in the queue. (Adds a `category` column — applied automatically on update.)
Bodholdt Tickets v0.14.0
  • Onboarding — a new “Channels & setup” panel at the top of the Ticketing admin page. It shows a setup checklist (do you have a form page, a portal page, agents, email intake, the AI co-pilot?) and spells out every way customers can reach you — the form + portal shortcodes (with “view your page” links) and pointers to the widget + email — plus a one-click “Create the support pages for me” button. Makes it obvious how to put ticket entry on the front end.
Bodholdt Tickets v0.13.0
  • Phase 4 (polish) — customer-facing pass on the submission form, customer portal, and embeddable widget. Delightful success states (an animated checkmark + warm copy) when a request is sent or a sign-in link is on its way; the widget now has a friendly chat-bubble launcher, properly labeled fields, focus-on-open, and closes on Escape; consistent look across all three. Accessibility + micro-interaction polish; no functional change to ticket handling.
Bodholdt Tickets v0.12.0
  • Phase 4 (packaging) — distribution-ready build. Adds a `build.sh` that produces a clean customer/WordPress.org ZIP (ships only the compiled Support Desk bundle, never the React source or build tooling), directory-index hardening (`index.php` silence files), a translation template, and a polished readme. No behavior change for existing installs.
Bodholdt Tickets v0.11.0
  • Phase 3 — gamification. The desk now plays like a game: agents earn XP for first responses, resolutions, and good CSAT; XP drives levels (with a progress bar), daily streaks, an all-time rank, a weekly + all-time leaderboard, and unlockable achievements. A stats chip sits in the Support Desk top bar; clicking it opens a leaderboard + achievements panel, and a satisfying “+XP” toast pops whenever you earn points. (Adds a bt_xp table — created automatically on update.)
Bodholdt Tickets v0.10.0
  • Phase 2.3 — embeddable cross-site widget. Paste a small snippet on ANY external website and a floating “Contact support” button appears; visitors file a ticket without leaving the page. The widget UI is isolated in a shadow DOM (host-site CSS can’t interfere), and submissions post cross-origin to a token-gated endpoint with an optional origin allowlist + honeypot + rate limit, landing on the “widget” channel. Configure + copy the snippet under Tickets → Embeddable widget.
Bodholdt Tickets v0.9.0
  • Phase 2.2 — customer portal. Add the [bodholdt_portal] shortcode to a page and customers can view, track, and reply to their own tickets. Works for logged-in WordPress users (uses their account email) and for customers without an account via a passwordless magic-link sign-in (enter email → receive a secure, single-use link → a 7-day session). Every view is strictly scoped to the signed-in customer’s email; internal notes and AI drafts are never shown. Magic-link requests are honeypot- and rate-limited and never reveal whether an email exists.
Bodholdt Tickets v0.8.0
  • Phase 2.1 — front-end submission form. Drop the [bodholdt_support_form] shortcode on any page and customers can file a ticket from your site (pre-filled for logged-in users). New tickets land in the desk on the “form” channel and flow into the same pipeline as email (auto-triage/draft if the co-pilot is on). Posts to a public REST endpoint protected by a honeypot + per-IP rate limit + validation; a clean, self-contained card that fits any theme; on-screen confirmation with the ticket reference.
Bodholdt Tickets v0.7.0
  • Phase 1.3 — thread summarize. A “✨ Summarize” button in the ticket header gives agents a one-click internal TL;DR for picking up or handing off a conversation: what the customer needs, what’s happened, current status, and the recommended next step. Includes internal notes for full context; shown in a dismissible banner above the thread; never sent to the customer.
Bodholdt Tickets v0.6.0
  • Phase 1.2 — the self-organizing desk. With Auto-pilot on (AI Co-pilot settings), every new ticket is analyzed by Claude the moment it arrives — in one call it sets the priority, adds topic tags, and pre-writes a reply that's waiting in the composer behind the “review before sending” ribbon when the agent opens the ticket. Tags show in the ticket header. Drafts are kept out of the visible conversation, generated at most once per ticket, and discarded once the agent sends. Analysis runs on a background job so inbound email stays fast; nothing is ever sent without a human.
Bodholdt Tickets v0.5.0
  • Phase 1 — the AI co-pilot (BYOK Claude). Agents click “Draft with AI” in the Support Desk and Claude drafts a reply using the ticket thread, the customer’s license context, and your brand voice + knowledge base. Drafts land in the composer under a “review & edit before sending” ribbon — a human always reviews and sends; nothing goes out automatically. Bring your own Anthropic API key (stored encrypted at rest); pick the model (defaults to Claude Opus 4.7); set brand voice + a knowledge base in Tickets → AI Co-pilot, with a one-click connection test. The stable system prompt (voice + KB) is prompt-cached for speed + cost across drafts.
Bodholdt Tickets v0.4.0
  • Phase 0.4 — email channel + configuration console (closes Phase 0). Outbound: agent replies email the customer via the site’s mail/SMTP, with a [#BT…] subject tag so their reply threads back. Inbound: a token-secured webhook endpoint (no IMAP needed) that any inbound-parse service (Postmark/Mailgun/SendGrid) or server forward can POST email to — new mail opens a ticket, replies thread by the subject tag, and quoted reply tails are trimmed. Console: a wp-admin page to set the desk name, from-name/email, enable in/out email, view + rotate the inbound webhook URL, and grant/revoke agent access. The core engine (license-aware tickets + REST API + Agent Support Desk + email + admin) is now complete; next up is the AI co-pilot.
Bodholdt Tickets v0.3.0
  • Phase 0.3 — the Agent Support Desk: a chrome-less, full-page React app at /support-desk/ (agent-gated) where agents work tickets. Tier-colored, triage-sorted queue (Open/Mine/All/Resolved + search); a conversation thread with distinct customer / agent / internal-note styling; a reply composer (with internal notes + ⌘↵ to send); assign + status + one-click Resolve; and a license-aware customer panel showing the customer's Bodholdt Licensing tier, active licenses, sites, and renewal inline. Built with React + Vite (bundle in assets/desk/). Cyberpunk-dark, matches the Labs catalog.
Bodholdt Tickets v0.2.0
  • Phase 0.2 — the `bt/v1` REST API (tickets list/get/create/reply, status, assign, csat, plus /me and /agents; all gated on the bt_agent capability) and the license-aware customer lookup that pulls a customer's Bodholdt Licensing tier, products, active licenses, and nearest expiry into every ticket. This is the spine the Agent Support Desk front-end (0.3) will talk to.
Bodholdt Tickets v0.1.0
  • Phase 0.1 — initial scaffold: plugin bootstrap, database schema (tickets, messages, attachments, tags, canned replies, activity log), and capabilities (bt_agent / bt_supervisor / bt_admin). Not yet feature-complete; activation creates the schema and a status page in the admin.
Bodholdt Licensing v10.19.1
  • Fun Pass follow-up. The "Issue New License" success card on the Licenses page now gets the same celebratory payoff as the dashboard's quick-create — a satisfying header, a key-reveal flourish, a bounce-in, and a confetti burst (which, like every animation in the plugin, respects your "reduce motion" setting). v10.19.0 had only upgraded the dashboard path.
Bodholdt Licensing v10.19.0
  • The Fun Pass — making the licensing admin feel like a game, not a chore. This release adds celebration, a sense of accomplishment over time, and a little personality to the highest-leverage moments — the first sale, issuing a license, a customer buying — without slowing any workflow. Every animated celebration respects the operating-system "reduce motion" setting (gated in JavaScript, not just CSS), and a new speaker toggle in the admin header controls celebration sounds (off by default).
  • FUN-CLS-01 — First-sale celebration + lifetime revenue. The Stripe checkout webhook now accumulates a running `cls_total_revenue` total and flags the very first completed sale. The Dashboard gains an animated "Earned $X" hero stat (empty state: "$0 — your first sale is going to feel great."), and the first time a sale lands, the next Dashboard load fires a one-time confetti burst and a "Your first sale! Someone out there is running your code right now." banner.
  • FUN-CLS-02 — Issuing a license now has a payoff. Quick Create on the Dashboard plays the bounce-in success animation (previously suppressed), reveals the key with a flourish, labels it "License #N minted ✦", and fires milestone confetti at the 10th, 50th, and 100th license.
  • FUN-CLS-03 — Warmer customer success page. Confetti on load (skipped for reduced-motion visitors) plus more human copy — "You're in! 🎉 … let's get you up and running" — in place of the old "Payment Successful!" receipt tone.
  • FUN-CLS-04 — The SDK "it's alive" moment. The SDK Generator's test result animates in with personality ("It's alive. Your plugin is now license-aware. 🔌"), and freshly generated code gets a celebratory header.
  • FUN-CLS-05 — Setup-completeness meter. An animated "Setup N/7 ✓" progress bar sits above the Dashboard checklist, with a celebratory state when everything's configured.
  • FUN-CLS-06 — In-character loading messages. "Creating…" / "Verifying…" rotate through friendlier lines ("Minting your key…", "Talking to Stripe…", "Conjuring your store pages…") with a rare easter-egg line. Purely cosmetic — the underlying action always fires immediately.
  • FUN-CLS-07 — Warmer empty/error copy. The "My Keys" no-results message and the Dashboard activity empty state are friendlier without losing any precision.
  • FUN-CLS-08 — Product "ready to sell" moment. A ready-to-sell product pill gives a green pulse and a "Ready for customers — share your store link." nudge with a one-click link to the storefront.
  • FUN-CLS-09 — A guide that knows where you are. Getting Started step headings show a "✓ done" badge once their underlying config is detected (Stripe connected, products added, pages generated, emails configured), and a "You're wired up — go make a sale! 🚀" finish line appears once the core setup is live.
  • FUN-CLS-10 — Celebration sound toggle. A small speaker control in the admin header stores a `cls_sound_enabled` preference (default OFF). Celebration sounds play only when it's on AND motion is allowed.
  • Accessibility: all confetti and celebration animations honor `prefers-reduced-motion` via `matchMedia` (no animated pieces are generated at all for reduced-motion users), and celebration audio is gated behind both that preference and the new sound toggle.
Bodholdt Licensing v10.18.2
  • CC-2 closure — consistent section-header styling. The new top-level Products page (added in v10.18.0) reintroduced one raw, inline-styled `<h2>` in its empty-state hero. Swapped it onto a new `.cls-empty-hero-title` utility class in `assets/admin.css`, so the admin UI now has zero raw/inline-styled section headers. This closes the last code-track item of the internal Steve Jobs Pass; the remaining audit item (an embedded 30-second setup screencast) is a content deliverable, not code.
Bodholdt Licensing v10.18.1
  • Post-feature polish patch — closes the eight deferred Steve Jobs / Grandma Test audit items (NEW-09 through NEW-17).
  • NEW-09 — Setup Wizard Step 3 now gates "Next" on a verified Stripe connection. Previously an operator could click straight past the Stripe step without ever verifying their key, then discover at the live checkout that payments 500. Step 3 now blocks "Next" until the key is verified (or already configured), with an inline nudge ("Please verify your Stripe connection to continue — or click 'Skip for now' to set this up later."). A new "Skip for now" affordance is the deliberate escape hatch so the wizard never traps an operator who wants to configure Stripe later.
  • NEW-10 — Hardcoded `/wp-admin/` URLs swept from the wizard. The Step 5 "Add Your First Product" / "Getting Started Guide" CTAs built `window.location.origin + '/wp-admin/'`, which breaks on subdirectory WP installs (`example.com/blog/wp-admin/`). Both now use the `clsAdmin.adminUrl` base localized via `wp_localize_script` (subdirectory-correct). The "Add Your First Product" CTA also now points at the new top-level Products page (`admin.php?page=bodholdt-licensing-products`) rather than the removed `Settings → Products` sub-tab.
  • NEW-11 — Setup Wizard modal is now screen-reader accessible. The wizard overlay gains `role="dialog"`, `aria-modal="true"`, and `aria-labelledby` pointing at its heading, plus a Tab/Shift-Tab focus trap and focus restoration to the triggering element on close — matching the existing `clsModal()` confirm-dialog accessibility pattern. Focus moves to the wizard on open and on each step change.
  • NEW-12 — Checkout error/success banner uses the frontend CSS-variable namespace. The success branch poked `style.borderColor` / `style.color = var(--cls-success)` — an admin-namespace variable that doesn't exist on the customer-facing storefront, so the banner rendered with no color. Switched to `var(--clsf-success, #459C51)`.
  • NEW-14 — Stripe SDK errors are mapped to friendly messages. The wizard's "Verify Connection", the Products-page "Create in Stripe", and Price-ID validation previously surfaced raw `\Stripe\Exception` text (e.g. `Invalid API Key provided: sk_live_...`) to the operator. A new `cls_friendly_stripe_error()` helper maps the common exception types (authentication, connection, permission/restricted-key, rate-limit, invalid-request) to plain-language guidance; the raw message is still written to the error log for debugging.
  • NEW-15 — Getting Started Guide copy updated for the current admin IA. Step 1 said "Settings → Stripe & License" (renamed to just "Stripe" back in v10.14.3); the product/version links pointed at the removed `Settings → Products` sub-tab. Both now read correctly and link to the top-level Products page.
  • NEW-16 — Customer-portal billing lookup no longer leaks customer type. The "manage billing" lookup returned three different success messages depending on whether the email was a Stripe subscriber, a trial/one-time customer, or unknown — letting an attacker enumerate customer status. All three branches now return one identical message that still surfaces the "My Keys" guidance to everyone. The billing-portal email is still sent only to genuine Stripe customers.
  • NEW-17 — Verification-code error message hardened. The lookup-code verify response is uniform whether the email is unknown, the code is wrong, or the code has expired, so it can't be used to confirm an account exists.
  • Maintenance: README `Stable tag` brought current (was stuck at 10.17.2; backfilled the 10.18.0 changelog entry below). Guarded a `json_decode(null)` PHP 8.3 deprecation notice in the Licenses table when a license has no registered domains.
Bodholdt Licensing v10.18.0
  • HEADLINE — Products promoted to a top-level admin tab. Products moved from a `Settings → Products` sub-tab to its own top-level page at `admin.php?page=bodholdt-licensing-products` (between Manage Licenses and Activity Log). Operators configure products repeatedly over a plugin's life, unlike the configure-once Stripe / License / Email settings; burying Products under Settings demoted it relative to the wizard's "Add Your First Product" CTA. The empty-state hero, inline Stripe Product-creation panel, product cards, version-management section, and JS templates all moved across faithfully.
  • NEW — first-class product bundles. A `cls_products` entry can now be a *bundle*: two optional fields, `grants_products` (an array of component product IDs) and `tier_override` (a tier key applied to each component license), turn one purchase into multiple license keys. The checkout webhook already issued multiple licenses natively — v10.18.0 adds the buy-URL expansion (`cls_resolve_bundle_payload()`) and the webhook tier-override read (`cls_resolve_component_tier()`) so each component license is issued at the chosen tier (e.g. both backup plugins at the 5-site Pro tier) rather than each component's root Solo tier. Graceful fallback if a tier doesn't exist on a component; cycle prevention so a bundle can't grant itself.
  • Operator UX: a per-card "This is a bundle" toggle reveals a component multi-select (pre-filtered to non-bundle products) plus a tier-override picker, hides the ZIP/trial/sites/version fields that don't apply to bundles, and shows a BUNDLE badge in the card header. New helpers: `cls_product_is_bundle()`, `cls_resolve_bundle_payload()`, `cls_get_product_by_id()`, `cls_resolve_component_tier()`.
  • Build-pipeline hardening: the build script now excludes `.fuse_hidden*` / `._*` / `.DS_Store` cruft from the shipped ZIP at both the rsync and ZipArchive layers.
  • Backwards-compatible: every change is `function_exists()`-gated and new metadata defaults via `??`, so pre-v10.18.0 checkout sessions hit the legacy path unchanged.
Bodholdt Licensing v10.17.2
  • Pricing-page logic patch — three regressions surfaced post-v10.17.1.
  • Fix: per-tier prices now actually differ. `cls_resolve_tier()` was referenced by the pricing-page shortcode since v10.9 but never defined anywhere. The `function_exists()` check fell through to `$tier = $product` (root product fields), so every tier_key returned the same root prices and all three tier cards on Bodholdt Licensing's Hobby/Studio/Foundry (and each backup plugin's Solo/Pro/Agency) showed identical prices regardless of which tier the customer picked. Fix: implement `cls_resolve_tier()` in `helpers.php` to read `$product['tiers'][$tier_key]` if present, returning the root entry for `'default'` tier. Operator action required: populate the `tiers` array in `cls_products` data via WP Admin → Bodholdt Licensing → Settings → Products. Without that, all cards still display the root price (this patch only fixes the algorithm).
  • Fix: Buy buttons now actually go to Stripe Checkout. The pricing page generated `?cls_buy=plugin&tier=pro&interval=yearly` URLs but no handler intercepted them anywhere in the plugin — clicking Buy loaded the homepage with the params hanging in the URL bar. Fix: new `cls_handle_buy_url()` hooked to `init` priority 5 in `class-cls-pricing-page.php` validates params, looks up the Stripe Price ID via `cls_resolve_tier`, creates a Stripe Checkout Session (subscription mode for monthly/yearly, payment mode for onetime), and redirects to Stripe's hosted checkout. Sets `cls_order_payload` metadata so the existing `checkout.session.completed` webhook in `class-cls-api.php` issues the license normally. Failure states redirect to `/buy-plugins/?cls_buy_error=<reason>` for operator diagnosis.
  • Fix: comparison chart now has room to breathe. Widened the pricing-page container `clspp-wrap` from 1180 to 1340px so the whole page gets more horizontal room. Comparison-table cell padding bumped from 9×12 to 12×16, font-size 0.9rem → 0.95rem, line-height 1.5, `min-width` 600 → 800, plus first column gets a 38% width allocation so multi-word feature labels like "Streaming restore (FK-safe, BLOB-safe)" stop wrapping. All sub-sections inside `.clspp-wrap` inherit the wider container so the page stays visually consistent top-to-bottom.
Bodholdt Licensing v10.17.1
  • Site bug patch — three regressions surfaced during v10.17.0 Verification B.
  • Fix: `[cls_pricing_page]` + `[cls_pricing_block]` shortcodes now actually register. `includes/class-cls-pricing-page.php` existed in the working tree but was never `require_once`'d by the bootstrap AND was untracked in git, so the file never shipped to prod and the shortcodes never loaded. Result on the live site: `/buy-plugins/` rendered the shortcode as literal text, every `/software/<plugin>/` product detail page had an empty buy-CTA section (no Purchase button anywhere). Fix: add the require to `custom-licensing-system.php`'s licensed-modules block + ensure the file is git-tracked so it deploys with the plugin.
  • Fix: theme `bl_starting_price()` no longer returns the lowest of monthly/yearly/onetime when callers want a yearly price. Previously, Solo backup with `monthly_price=3.99 / yearly_price=29 / onetime=69` returned `$3.99` and the homepage card template rendered it as `$3.99 / yr` (since the period is hardcoded). Same bug on Bodholdt Licensing's Hobby tier (`$5.99/mo` showed as `$5.99/yr`). New algorithm: prefer the lowest `yearly_price` across the product's root fields AND any nested `tiers`. Fall back to `onetime_price` (lifetime — acceptable on a /yr card as a single-purchase alternative). Last resort: `monthly_price × 12` (annualized). Function now matches the marketing intent every caller expects. Theme file `themes/bodholdt-labs/functions.php` (~30 LOC swap).
  • Post-ship requirement: clear the `bl_starting_price_*` transient cache so the new price values render immediately. `wp transient delete --all` covers it; the ship procedure script does this.
Bodholdt Licensing v10.17.0
  • HEADLINE — Stripe auto-create from the Products tab. Replaces the manual "go to Stripe dashboard, create a Product, create monthly/yearly/lifetime Prices, copy three `price_…` IDs, paste them back in this form" loop with one in-plugin step. Click *Create new product in Stripe*, enter Name + Description + the prices you want, and the plugin calls `\Stripe\Product::create()` + up to three `\Stripe\Price::create()` calls behind the scenes, then drops a pre-filled Product card into your list with the returned IDs. Manual entry remains available via *Advanced — use existing Stripe products* for operators with existing Stripe inventory. New AJAX endpoint `cls_stripe_create_product` (manage_options + admin nonce gated). Includes partial-create rollback: if Product creation succeeds but a Price creation fails, the orphaned Product is archived (`active: false`) so Stripe Dashboard doesn't accumulate junk.
  • Email correctness pass (NEW-01 / NEW-02 / NEW-13). Adds new `cls_send_mail()` helper that wraps `wp_mail()` with a branded `From: <Brand> <noreply@host>` header (operator-overridable via the new `cls_email_from_name` / `cls_email_from_address` / `cls_email_reply_to` options), a `Reply-To:` header pointing at the operator's admin email by default, and a plain-text `AltBody` via `phpmailer_init` for multi-part email correctness. Migrates every customer-facing send through the new helper: trial-checkout, paid-purchase webhook, verification-code lookup, billing-portal access link, admin payment-failure notification, daily expiry reminder, "Email License to Customer" admin action, and Send Test Email. Fixes the silent template-discard bug where the trial-checkout and paid-purchase webhook paths built the operator-customized `cls_email_trial_body` / `cls_email_purchase_body` template into a local variable and then never used it — real customers received a hardcoded license-block instead of the customized template (the Send Test Email path correctly used the template, so operators tested green and shipped broken). v10.17.0 routes both paths through the new `cls_render_email_from_template()` helper which substitutes `{licenses}`, `{email}`, `{manage_url}` into both HTML and plain-text bodies.
  • First-time-user-experience FTUE pass (NEW-06 / NEW-07 / NEW-08). Adds `plugin_action_links_*` filter so the plugin row on `wp-admin/plugins.php` now surfaces Setup Wizard / Settings / Getting Started links next to Deactivate (was previously bare — operators had to hunt the new sidebar item themselves). Adds a one-shot post-activation admin notice — "Bodholdt Licensing activated. Run the 5-minute Setup Wizard to connect Stripe and create your first product. [Start Setup Wizard] [Open Getting Started Guide] [Skip for now]" — that auto-dismisses on first dismiss or after 30 minutes, suppressed on Bodholdt Licensing's own admin pages, gated on `manage_options`. Adds smart-default storage path (`wp-content/uploads/cls-vault`) auto-created at activation with Apache 2.4+ `Require all denied` `.htaccess` + an HTTP 403 `index.php` for defense-in-depth — Dashboard checklist now starts green on Day 1 instead of showing a red ✗ with no way for the operator to know what path to type.
  • Settings hardening (NEW-03 / NEW-05). Stripe Secret Key and Webhook Signing Secret save handlers now surface explicit errors when the key prefix doesn't match — previously, pasting a restricted key (`rk_…`) or a publishable key (`pk_…`) silently failed while the page still said "Settings Saved." Now the operator sees "Stripe Secret Key was not saved — it must start with sk_test_ or sk_live_. Restricted keys (rk_…) and publishable keys (pk_…) are not yet supported." with a hint pointing at the right Stripe dashboard section. License-key placeholder text standardized to `XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX` (4×8 hex) across the Setup Wizard, Settings → License, and the SDK Generator test panel, matching the actual format `cls_format_license_key()` returns (was previously a mix of 4×4 and 8×4 examples that didn't match real keys).
  • P3-E — Customer Portal email autofill. When a customer completes checkout, a 60-day `cls_recent_purchase_email` cookie is set via JS on the success page render (using `SameSite=Lax` and the `Secure` flag on HTTPS). On the Customer Portal's next visit, the plugin renders a "We recognize this email from a recent purchase: [email protected] — [Continue with this email] / [Use a different email]" banner above the form, pre-fills the email input, and lets the customer skip retyping. Does NOT bypass the verify-code / billing-portal authentication flow — purely a UX shortcut.
  • CC-3 — Keyboard shortcuts (progressive disclosure). Press `?` on any Bodholdt Licensing admin page to reveal an overlay listing the available shortcuts. `g` followed by `d`/`l`/`s`/`g`/`k`/`a` navigates to Dashboard / Licenses / Settings / Guide / SDK Generator / Activity log (vim-style). On the Licenses table specifically, `j`/`k` move row selection up/down, `e` triggers Edit, `r` Revoke, `d` Delete. Suppressed while typing in inputs / textareas / selects / contenteditable elements; suppressed when meta/ctrl/alt are held. Esc closes the overlay or clears row selection. Scoped to plugin admin pages only via body-class + DOM-marker detection so shortcuts don't leak into the rest of WP admin.
  • Theme polish — bodholdt-labs CSS-variable injection. The `bodholdt-labs` cyberpunk-dark theme's `.cls-portal-wrapper` / `.cls-checkout-wrap` block at line 1924+ now opens with a CSS-variable injection block that maps the theme's tokens (`--bg-card`, `--text-primary`, `--neon-cyan`, etc.) INTO the plugin's v10.16.0 `--clsf-*` namespace. The legacy `!important` overrides remain in place as belt-and-suspenders until the plugin migrates remaining structural rules (padding, layout) to variables — both apply simultaneously without conflict. Cleaner integration; no functional change at this stage.
  • NEW-04 — Empty-state for Products tab. When no products are configured, the Products sub-tab now shows a friendly hero callout: "Add your first product. Each product is one plugin or theme you sell. The fastest path is to let Bodholdt Licensing create the Stripe Product and Prices for you in one step — no need to leave this page. [Create new product in Stripe (recommended)] [Advanced — use existing Stripe products]". Previously the panel rendered three buttons (Add Product / Save All / Generate Pages) over a blank area with zero copy — a non-developer had no idea which to click first. The redundant Save / Generate buttons are hidden until at least one product exists.
  • clsAdmin.adminUrl localized via `wp_localize_script` so the wizard's CTAs + keyboard shortcuts build correct paths on subdirectory WP installs (`example.com/blog/wp-admin/`). Full NEW-10 hardcoded-URL sweep deferred to v10.18.x.
Bodholdt Licensing v10.16.0
  • NEW: assets/frontend.css — a new dedicated stylesheet for the three customer-facing shortcodes (cls_checkout_form, cls_success_page, cls_portal). Previously these embedded their CSS via 3 inline `<style>` blocks + 76 inline `style="..."` attrs scattered across `includes/class-cls-frontend.php`. v10.16.0 extracts all sweepable CSS into one well-organized stylesheet that loads on any page containing one of the 3 shortcodes via `wp_enqueue_style`. The 10 remaining inline-style attrs are all intentional: 7 in HTML email templates (where clients like Gmail / Outlook strip external stylesheets), 2 in SVG path-specific animations tied to stroke-dasharray for the success-page checkmark draw-in, 1 false-positive grep match in a ship-note comment. Effectively 100% of sweepable inline styles removed.
  • NEW: theme-agnostic CSS-variable architecture. All colors, typography, spacing, border-radius, and shadow values in frontend.css are CSS variables on `:root` (`--clsf-bg`, `--clsf-text`, `--clsf-primary`, `--clsf-accent`, `--clsf-border`, `--clsf-radius-md`, etc.). Host themes can re-skin the customer-facing shortcodes by declaring their own variable values in a scoping selector — no `!important` patches needed. Example for a dark-themed host: `.my-dark-theme .cls-wrapper, .my-dark-theme .cls-portal-wrapper, .my-dark-theme .cls-success-wrapper { --clsf-bg: rgba(255,255,255,0.03); --clsf-text: #f0f0f5; --clsf-border: rgba(255,255,255,0.08); }`. Defaults are a clean light-mode design using the official Bodholdt Labs brand palette — works on any WordPress theme (Astra, Twenty Twenty-Four, Storefront, custom).
  • NEW: ~50 utility classes added to assets/frontend.css covering all 3 customer-facing shortcodes. Checkout: `.cls-wrapper`, `.cls-form-section`, `.cls-label`, `.cls-input`, `.cls-row`, `.cls-price-display`, `.cls-price-free`, `.cls-remove-btn`, `.cls-add-btn`, `.cls-checkout-btn`, `.cls-btn-spinner`, `.cls-totals`, `.cls-grand-total`, `.cls-total-amount`, `#cls-error-banner`, `.cls-error-banner-dismiss`, `#cls-confirm-box` + nested classes, animations (`cls-spin`, `clsSlideIn`, `clsPriceFlash`, `clsModalEnter`), `.cls-stripe-attribution`, mobile media query. Success page: `.cls-success-wrapper`, `.cls-success-icon`, `.cls-success-title`, `.cls-success-sub`, `.cls-success-downloads` + title/hint, `.cls-license-card` + `.cls-license-card-download`, `.cls-success-expired` + title/body, `.cls-success-whatsnext` + title/list, `.cls-success-actions` + `.cls-success-btn-primary/secondary`, animations (`cls-success-enter`, `cls-circle-draw`, `cls-check-path`). Portal: `.cls-portal-wrapper`, `.cls-portal-title`, `.cls-portal-intro`, `.cls-portal-field`, `.cls-portal-options`, `.cls-portal-option-card` + meta, `.cls-portal-btn`, `.cls-portal-secondary`, `.cls-verify-step`, `.cls-verify-prompt`, `.cls-verify-row`, `.cls-verify-btn`, `.cls-resend-btn`, `.cls-verify-help`, `.cls-wrong-email-link`, `.cls-portal-msg` + `--success`/`--error` variants, `.cls-portal-banner` + `--success`/`--error` variants, `.cls-license-result` + `.cls-lr-meta`/`-form`/`-download`/`-empty`, `.cls-results-box` + `-title`, `.cls-skeleton-row` + shimmer animation, `.cls-card-block` + `--warning` variant for empty-state / maintenance cards.
  • REFACTORED: enqueue logic — previously `wp_register_style('cls-checkout-styles', false)` then `wp_add_inline_style` injected ~80 lines of CSS at runtime. Replaced with proper `wp_enqueue_style('cls-frontend', ..., CLS_VERSION)` so the browser can HTTP-cache the stylesheet and version-bust on plugin update via the existing CLS_VERSION cache-buster. Load condition expanded: previously checked only for `cls_checkout_form`; now loads on any of `cls_checkout_form`, `cls_success_page`, or `cls_portal` (success + portal pages used to inherit the inline `<style>` blocks within their own shortcode output).
  • Closes CC-4 (Customer-facing portal page styling decoupled from cls-admin brand) from our internal design review. The customer portal at `/manage-subscription/` and its companion shop / success pages no longer ship as a tangle of inline styles — they share one cohesive stylesheet that's themeable, cacheable, and shippable to WP.org.
  • HOST-THEME NOTE: the `bodholdt-labs` cyberpunk-dark theme that powers `bodholdtlabs.com` (where these shortcodes render in production) has existing `!important` overrides on `.cls-portal-wrapper` / `.cls-checkout-wrap` at theme line 1924+. Those overrides still work as-is post-v10.16.0; a future ship can refactor them to use the new CSS variable injection pattern for cleaner theme integration.
Bodholdt Licensing v10.15.2
  • NEW: page-guide.php inline-styles swept — 89 → 0 (100% reduction). Every inline `style="..."` attribute removed. ~12 new utility classes added to cls-admin.css for the Getting Started Guide page: welcome banner (`.cls-guide-wrap` / `.cls-guide-welcome` / `.cls-guide-welcome-title` / `.cls-guide-welcome-sub`), progress card (`.cls-guide-progress-card` / `.cls-guide-progress-head` / `.cls-guide-progress-label` / `.cls-guide-progress-meta` / `.cls-guide-progress-track` / `.cls-guide-progress-fill`), reset button (`.cls-guide-reset-btn`), per-step header pattern (`.cls-guide-step-header` already existed; added `.cls-guide-checkbox` for the 18px green-accented checkbox and `.cls-guide-step-num` base + 6 color modifiers `--blue/green/purple/orange/teal/lime` for the numbered circles — lime variant uses navy text for contrast, the rest use white), `.cls-h2-flush` for `margin:0` H2 inside the step header, `.cls-guide-list` for the line-height:2 ordered lists, `.cls-link-cyan` for brand-cyan anchors (heavily reused), `.cls-info-box-grey-sm` + `.cls-info-box-grey-fs13` variants for the 4 grey-on-grey info boxes inside steps, `.cls-page-grid-3` + `.cls-page-card-grey` + `.cls-page-card-meta` + `.cls-page-card-code` for the Step-3 three-column grid (responsive: collapses to 1col @ ≤700px), `.cls-copy-wrap-md` + `.cls-input-mono-grey` for the webhook URL copy widget, `.cls-table-bare` + `.cls-table-label-col` + `.cls-table-label-col-200` for the Quick Reference widefat table, `.cls-faq-item` + `.cls-faq-q` + `.cls-faq-a` for the 6 FAQ entries. v10.15.2 also added `.cls-guide-sublist` (`list-style:disc; padding-left:20px; margin:4px 0`) for Step 4's nested ul.
  • NEW: page-settings.php inline-styles swept — 81 → 1 (98% reduction). Only the dynamic vault-indicator div retains an inline `style` because the background / border / color values are PHP-computed at runtime from the `$is_secure_basement` boolean + the `$status_color` variable. Everything else moved to utility classes: License sub-tab intro sub-text + license-key input + status fw-semibold span, Stripe sub-tab help-cyan note (replacing inline `background:#f0f8ff; border-left:3px solid #00BCF2; ...`) + link-cyan-bold + form-input-full (Secret + Webhook Secret inputs) + help-cyan-note-sm (the in-form light-blue test/live-mode description) + cls-text-red (Not configured states) + cls-mt-sm (verify-button rows) + verify-status + webhook-test-status + cls-input-mono-grey-full (the webhook URL + storage-path inputs) + cls-status-msg-ok/warn/err (Folder Found / not-writable / not-found spans) + cls-vault-indicator (the dynamic-color status panel) + cls-currency-select + cls-page-slug-label + cls-page-slug-input. Emails sub-tab: cls-placeholder-btn-mono (the `{licenses}` `{email}` `{manage_url}` buttons) + cls-email-h3-flex (Reset-to-Default flex header) + cls-fw-semibold (Subject/Body labels) + cls-email-subject-input + cls-email-body-textarea + cls-email-actions-row + cls-email-preview-mt. Products sub-tab: cls-products-actions (top button row) + cls-btn-cyan-primary (Add Product) + cls-card-h3-white + cls-card-num-faded + cls-card-name-white (the dark card header) + cls-file-found / cls-file-missing (✓/✗ vault-file status) + cls-mt-sm (upload zone) + cls-product-grid-2 (Trial Days + Sites Allowed 2-col layout) + cls-field-help-flex (the 3 "Where do I find this? / Validate" rows on Monthly/Yearly/Lifetime price IDs) + cls-btn-validate + cls-card-field-mt + cls-changelog-textarea + cls-actions-row-mt + cls-add-product-btn. Branding + Advanced: cls-brand-name-input + cls-checkout-btn-input + cls-h2-navy (Settings Export/Import + Admin Notices headers) + cls-export-row + cls-import-file-mr + cls-desc-mt + cls-notice-form-row + cls-notice-form-fields + cls-notice-msg-col + cls-notice-label-block + cls-notice-msg-input + cls-btn-clear-red + cls-notice-list-mt + cls-notice-item-pad + cls-desc-mt-sm. Bonus: the Admin Notices section's `style="border-top:3px solid #FF8C00;"` got replaced with the existing `data-border="orange"` attribute now that v10.15.2 extends data-border to all brand colors.
  • NEW: cls-section[data-border] attribute extended to ALL brand colors. Pre-v10.15.2 only blue/green/purple/orange/navy were supported as `data-border` values; teal/lime/red/pink/yellow had to be inlined as `border-top:3px solid #XXXXXX;`. v10.15.2 adds the missing 5 selectors so every brand color is reachable via the same attribute pattern: `data-border="teal|lime|red|pink|yellow"`. page-guide.php Step 5 (teal) + Step 6 (lime) + page-settings.php Admin Notices section (orange) now use the attribute instead of inline.
  • CUMULATIVE phase 1+2+3 reduction across all 6 admin pages: 317 → 26 inline styles (92% reduction). Per-file: page-dashboard.php 34 → 5 (85%), page-activity.php 26 → 10 (62%), page-generator.php 37 → 2 (95%), page-licenses.php 50 → 8 (84%), page-guide.php 89 → 0 (100%), page-settings.php 81 → 1 (98%). All 26 remaining are intentional: dynamic PHP-computed values (vault-indicator colors, grace-period bar fill, status pill color, file-found state) + table-column widths (contextual to specific table layouts where extracting to classes would obscure rather than help). The cyberpunk-dark refresh inline-styles sweep (CC-1) is now COMPLETE across all admin surfaces.
Bodholdt Licensing v10.15.1
  • NEW: page-generator.php inline-styles swept — 37 → 2 (95% reduction). New utility classes added for the SDK Generator: info-card pattern (Generator Instructions + What This Does panels), SDK step headers (Step 1/2/3), SDK code blocks (dark navy/lime for generated SDK + light grey for init snippet), prefix-preview callout (v10.14.1 P1-E), Test SDK input row + result states (info/ok/fail/err — v10.14.4 P3-D), `<details>` summary + pre styling. Remaining 2 inline styles: 1 `display:none` for the test-result div initial state (could class but JS toggles inline anyway) + 1 `margin-top:30px` on the outer post-submit wrapper (one-off).
  • NEW: page-licenses.php inline-styles swept — 50 → 8 (84% reduction). New utility classes added for the Licenses page: Issue New License card + form + field-flex layouts (extends v10.15.0's existing `cls-create-license-card` pattern), expiry quick-pick chips (`.cls-chip` + `.cls-chip-row`), Issue success / error state cards, license-creation-disabled warning card, License Database gradient header + lime records-badge, license search-bar + bulk-actions row, pagination text + button row, success-notice green-border + new-key inline-flex copy-wrap + send-key-to-customer button alignment. Remaining 8 inline styles are ALL table-column widths (3% / 22% / 18% / 14% / 12% / 7% / 8% / 13%) — contextual to the licenses-table layout, intentionally inline. Preserved all v10.14.1 P1-D AJAX success card + P2-E txn_id details + P2-G admin-email pre-fill + P3-B expiry chips.
  • NEW: ~25 additional utility classes added to cls-admin.css for SDK Generator + Licenses-page-specific patterns. Categories: info-card pattern (cls-info-card / cls-info-card-navy / cls-info-card-green / cls-info-card-grid), SDK code blocks (cls-sdk-code-dark / cls-sdk-code-light), SDK step headers (cls-sdk-step-header / cls-sdk-step-header-mt), prefix-preview box (cls-prefix-preview-box / cls-prefix-preview-code), Test SDK states (cls-test-input-row / cls-test-key-input / cls-test-result / cls-test-result-info/ok/fail/err / cls-test-details / cls-test-details-summary / cls-test-details-pre), Issue New License layout (cls-issue-license-card / cls-issue-license-form / cls-issue-field / cls-issue-field-fixed / cls-issue-success / cls-issue-error / cls-issue-h2 / cls-issue-h2-orange), expiry chips (cls-chip / cls-chip-row), disabled-card (cls-disabled-card / cls-disabled-meta), License Database header (cls-page-h1-gradient / cls-records-badge), notice-border extension (cls-notice-green-border), width utility (cls-w-full), form section card (cls-form-section-card), Licenses filter/bulk/pagination (cls-license-search-input/select/form / cls-license-filter-wrap / cls-license-bulk-row/select / cls-pagination-text / cls-pagination-buttons), code-actions row (cls-code-actions), and a handful of misc helpers (cls-inline-flex-auto / cls-license-key-lg / cls-ml-md / cls-ml-auto / cls-no-products-warn).
  • VISUAL CHANGE ADVISORY: the License Database gradient `<h1>` header at the top of the Licenses page is now powered by `linear-gradient(135deg, var(--cls-navy), var(--cls-purple))` which evaluates to the v10.15.0 re-anchored navy `#00188F` (was inline `#00188F` already — exact match, no visual change here). The lime records-badge inside the H1 uses `var(--cls-lime)` (`#BAD80A`, brand-exact). The SDK Generator's Step 1 generated-code textarea is now `background: var(--cls-navy)` with `color: var(--cls-lime)` — was hardcoded `#00188F`/`#BAD80A` inline, so visual is identical except the navy is now uniform across the admin (no more drift between this code-block and other navy-using components).
Bodholdt Licensing v10.15.0
  • NEW: CSS-variable palette re-anchored to the official Bodholdt Labs brand. Pre-v10.15.0 the cls-admin.css `:root` palette had drifted off-brand on 3 colors (--cls-navy was #0B1467 instead of brand #00188F; --cls-green was #2d9d4e instead of brand #459C51; --cls-orange was #e68a00 instead of brand #FF8C00). The drift was historical accident — the older hand-typed inline-style hex codes scattered through the admin pages were ON-brand all along; only the newer "cyberpunk-dark" CSS-variable palette drifted. v10.15.0 re-anchors --cls-navy, --cls-green, --cls-orange to the brand values, and adds TWO new variables (--cls-yellow #FDF250, --cls-pink #D82E8A) plus their `-soft` background variants and an explicit --cls-black. Components in cls-admin.css currently using `var(--cls-navy)` / `var(--cls-green)` / `var(--cls-orange)` will visually re-tint to the brand shades — back to brand-correct.
  • NEW: 30+ utility classes added to cls-admin.css for the v10.15.0 admin inline-styles sweep. Text colors (cls-text-navy / cls-text-cyan / cls-text-green / cls-text-orange / cls-text-red / cls-text-purple / cls-text-muted / cls-text-faint / cls-text-meta), font sizes (cls-text-xs / cls-text-sm / cls-text-md), font weights (cls-fw-bold / cls-fw-semi), margin utilities (cls-mt-xs/sm/md/lg/xl + cls-mb-xs/sm/md/lg/xl + cls-m-0), flex layout primitives (cls-flex-row / cls-flex-row-md / cls-flex-row-end / cls-flex-between / cls-flex-wrap / cls-stat-row), brand-tinted buttons (cls-btn-green + cls-btn-green-lg modifier extending the existing cls-btn-cyan / cls-btn-navy), status pill (cls-status-pill with dynamic background staying inline), info boxes (cls-info-warn + cls-info-warn-text), code block (cls-code-block), bare card (cls-card-bare), quick-action group (cls-quick-action-group), mono small (cls-mono-sm), form helpers (cls-form-label-block / cls-form-input-flex), dashboard checklist details (cls-check-detail-toggle / cls-check-detail-meta / cls-check-action-row), empty-cta utilities (cls-empty-cta / cls-empty-cta-icon / cls-empty-cta-title / cls-empty-cta-desc), responsive 2-col grid (cls-dash-2col with cls-empty modifier), plus a few one-offs (cls-self-end / cls-hidden). Sets the foundation for v10.15.0a (page-generator + page-licenses) and v10.15.0b (page-guide + page-settings) which will reuse + extend these classes.
  • NEW: page-dashboard.php inline-styles swept — 34 → 5 (85% reduction). Remaining 5 are justifiable: 2 dynamic PHP values (grace-period bar fill, status pill background color), 3 table-column widths (contextual to the recent-activity table layout).
  • NEW: page-activity.php inline-styles swept — 26 → 10 (62% reduction). Remaining 10: 5 table-column widths, 1 dynamic PHP value, 4 minor spacing one-offs.
  • VISUAL CHANGE ADVISORY: components on every admin page that use --cls-navy / --cls-green / --cls-orange will subtly re-tint from the pre-v10.15.0 shades to brand-correct shades. This is intentional, not a regression. If a specific component looks off after deploy, the fallback is to re-anchor that ONE component's CSS rule to a legacy color rather than reverting the whole CSS-var change. Worst case: revert the v10.15.0 commit and stay on v10.14.4 until the visual issue is resolved.
Bodholdt Licensing v10.14.4
  • NEW: Getting Started Guide progress now syncs across devices. Pre-v10.14.4, each step's done-state was stored in browser `localStorage` — meaning an operator who marked 3 of 6 steps complete on their Mac saw 0 of 6 from their iPad the next day, and there was no way to know whether they'd actually done the work or just forgotten which browser they used. v10.14.4 persists progress to per-user WP meta (`cls_guide_completed_steps`) via a new `cls_guide_progress_set` AJAX endpoint, so the operator's progress travels with them across devices, AND multi-operator stores don't clobber each other (per-user, not per-install). One-time localStorage → user-meta migration runs on first load if the operator had pre-v10.14.4 state in their browser. Reset progress button now clears both the in-memory state and the server-side meta.
  • NEW: SDK Generator "Verify It Works" Step 3. After clicking Generate Code, operators now see a third section below Step 1 (Create The File) + Step 2 (Hook It Up): paste a license key, click 🧪 Test SDK, and a new `cls_test_generated_sdk` AJAX endpoint simulates the exact `cls_check` call the generated SDK would make against the live server via `wp_remote_post`. Returns a green "✓ SDK works." with the license's expiry + domain-registration status on success, or a red "✗ SDK check failed." with the server's error message + raw JSON response (expandable for debugging) on failure. Closes the loop in the same admin session — operator can validate the SDK actually resolves real license keys before committing the code to their plugin.
  • Audit P3-F (version badge in admin shell) WITHDRAWN on baseline inspection. A `<span class="cls-version-tag">v10.14.x</span>` badge already exists at `class-cls-admin.php:161` inside the `cls_render_admin_tabs()` function — it appears on every Bodholdt Licensing admin page next to the brand header. The internal design review was wrong; no code change needed. Same correction pattern as v10.14.1 P3-C, v10.14.3 P2-C/P2-D.
Bodholdt Licensing v10.14.3
  • NEW: Settings sub-tab "Stripe & License" split into separate "License" + "Stripe" panels. Pre-v10.14.3 the first sub-tab housed THREE unrelated concerns crammed onto one screen: (1) License Status (your Bodholdt Licensing home-license key + grace-period state), (2) Stripe & Payment Configuration (Secret Key + Webhook + Currency + Page Slugs + Storage Path), (3) a Storage vault security indicator. Steve Jobs Pillar 3 ("every screen should have ONE primary action that visually dominates") was violated by having 2-3 primary actions per screen. v10.14.3 splits the navigation: License is now its own sub-tab (primary action: paste + save license key), Stripe keeps the Stripe & Payment Configuration form intact (primary action: paste + Verify Connection). The Storage Path field + its contextual vault-security indicator stay paired inside the Stripe form (moving them to Advanced would orphan the indicator from the input it describes — the audit's original suggestion was reconsidered on inspection). Also removed dead-code re-definition of `$is_secure_basement` / `$status_color` / `$status_msg` (5 vestigial lines that re-set variables already defined near the top of `cls_settings_page()`).
  • NEW: Stripe sub-tab inline "Show Step-by-Step Setup Guide" disclosure replaced with a single source-of-truth link. Pre-v10.14.3, a `<button>` toggle revealed a nested 4-step list inside the Stripe sub-tab — duplicating the same content already maintained on the dedicated Getting Started Guide page (`/wp-admin/admin.php?page=bodholdt-licensing-guide`). Two sources of truth drift over time. Now: a single inline blurb *"Need help connecting Stripe? See the [Getting Started Guide →] for the full 4-step walkthrough"* on a soft-blue background. The Guide page is the canonical reference; the Settings page links to it.
  • NEW: Dashboard "Plugin License" Configure button now points to the dedicated License sub-tab. Was `#cls-sub-stripe` (pointing at the old combined Stripe & License sub-tab); now `#cls-sub-license` reflecting the new split. The other 3 Dashboard sub-tab anchors (Stripe Secret Key, Stripe Webhook Secret, File Storage) continue to point at `#cls-sub-stripe` because all 3 fields live in the Stripe form panel.
  • Audit P2-C (Email-template live preview) and P2-D (Send Test Email button) WITHDRAWN on baseline inspection. Both were ALREADY shipped in the pre-v10.14.0 code: `clsUpdateEmailPreview()` at admin.js:823-862 does full token substitution + binds to `input` events on subject/body fields, and `clsSendTestEmail()` at admin.js:611 plus the Send Test buttons at page-settings.php:370 + 392 plus the `cls_send_test_email` AJAX endpoint at class-cls-ajax.php:48 form a complete circuit. Both defects don't exist in production. Same lesson as v10.14.1 P3-C: when an audit hypothesizes missing infrastructure, grep for the corresponding window-bound function or AJAX endpoint before claiming the defect.
Bodholdt Licensing v10.14.2
  • NEW: Setup Wizard Step 1 welcome copy rewritten for non-developer operators. Pre-v10.14.2 opener: *"This wizard will help you configure your license server in a few simple steps. You'll need your Stripe API keys ready."* — assumed the operator already had a Stripe account + located their API keys. ~80% of small-business owners considering Bodholdt Licensing have neither. New copy: *"We'll walk you through everything in about 5 minutes — paste your license key, connect Stripe (we'll show you how if you don't have an account yet), and create the three pages your customers will use to buy and manage their subscriptions. No technical experience needed."* Plus a small reassurance below: *"First time using Stripe? We'll help you set it up in Step 3."*
  • FIXED: Setup Wizard Step 2 "Save & Next" no longer silently advances on AJAX failure. Pre-v10.14.2, if the license-key + currency save AJAX failed (network blip, server-side rate-limit, license-server outage), the wizard advanced anyway to Step 3 — operator thought their data saved when it didn't. Now: on AJAX failure, an inline error appears in the existing `#cls-wiz-step2-status` div ("Save failed — please try again." or "Network error — check your connection and try again.") and the wizard does NOT advance. Operator can correct + retry.
  • NEW: Setup Wizard step-progress labels gracefully degrade on narrow viewports. Pre-v10.14.2, the 5-step progress bar ("Welcome / License / Stripe / Pages / Done") wrapped awkwardly on tablet-portrait widths or in collapsed-sidebar WP Admin layouts. Now: `@media (max-width: 640px)` hides the labels and relies on the existing `title` attribute for tap-tooltip discoverability. Step bars get slightly thicker (6px → 8px) on narrow viewports to compensate for the removed labels.
  • NEW: Setup Wizard Step 4 explains what each generated page is for. Pre-v10.14.2, the body said *"We'll create three WordPress pages for your store: a checkout page, a success page, and a customer portal. You can customize them later."* Then a tiny 12px grey footer line listed the page names. Now: an explicit bullet list inside the body shows each page's URL slug + a one-line role description (e.g. */buy-plugins* — your storefront / */purchase-success* — the thank-you page after checkout / */manage-subscription* — where customers look up their keys, cancel, or upgrade). Operator knows what they're creating BEFORE clicking Generate Pages.
  • NEW: Setup Wizard Step 5 has one primary CTA, not two competing ones. Pre-v10.14.2, Step 5 had a callout box pointing at the Getting Started Guide AND a "Add Your First Product →" primary footer button — two CTAs visually competing for attention. Steve Jobs Pillar 3: every screen should have ONE primary action that dominates. The Guide link is now a small "Need more help?" footer affordance below the primary CTA, properly subordinated.
Bodholdt Licensing v10.14.1
  • NEW: Issue New License is now AJAX-driven with a satisfying success card. Pre-v10.14.1, generating a manual license submitted a form, reloaded the page, and the new license was just another row in the table below — you had to scroll, find your new row, select the key text, copy it, and email it to the customer. v10.14.1 intercepts the form submit, fires the `cls_quick_create_license` AJAX (which already existed), and renders a green confirmation card inline: *"✓ License created!"* with the formatted key in a copy-button-paired code block, *"Emailed to <[email protected]> ✓"*, and two action buttons — Issue another (keeps the email pre-filled because operators typically issue multiple keys to the same person) and View all licenses ↓ (page reload to see the new row in the table). Non-JS fallback path is preserved — the legacy POST handler still runs if JavaScript is disabled.
  • NEW: Issue New License pre-fills the operator's admin email. Pre-v10.14.1, the User Email field had `[email protected]` as a placeholder (visual only, not actually pre-filled). Now the field is pre-populated with `wp_get_current_user()->user_email` (falling back to the `admin_email` option). Operators issuing comp licenses or replacements to themselves no longer retype. Clear the field to type a different recipient as before.
  • NEW: Issue New License has expiry quick-pick chips. "Never", "30 days", "90 days", "1 year" — one click sets the date picker to today + N days (or clears it for Never). Covers the common operator workflows (apology comp, beta period, lifetime grant) without clicking through the date picker.
  • NEW: SDK Generator shows a live class-name preview as you type. Type "BodholdtBackup" in the Unique Prefix field and a blue callout below appears: *"This will generate the class: `BodholdtBackup_Licensing_Client`"*. Updates on every keystroke. Sanitization matches the server-side normalization so the preview matches the actual generator output. Non-developer operators can validate their prefix before clicking Generate Code.
  • NEW: SDK Generator has a "what this does, in plain language" panel. Side-by-side with the existing Generator Instructions card, a green-bordered explanation panel: "We'll write you a PHP file that connects your plugin to your Bodholdt Licensing server. It checks license keys, blocks unlicensed installs from auto-updating, and shows your customers a friendly setup screen. Drop the file into your plugin's includes/ folder and add one require_once line — we'll show you exactly what. Total install time: about 2 minutes. No prior SDK knowledge needed." De-risks the page for first-time customers.
  • NEW: Licenses table hides raw Stripe txn_id behind a `<details>` affordance. Pre-v10.14.1, every subscription row showed `Sub: sub_1TWmZHG7DhP9pK2L...` as plain grey text below the license key. That ID is useful exactly once (when an operator needs to look up a subscription in the Stripe dashboard to diagnose a payment issue). The rest of the time it was visual noise on a high-density row. Now collapsed behind a *"Stripe details"* expander; click to reveal the `txn_id` + a *"Open in Stripe →"* deep-link to the Stripe Dashboard (auto-routes to test-mode dashboard for `sub_test_*` subscriptions). Same treatment for the raw product-slug shown below the product name — now behind a *"Slug"* disclosure.
  • Note on P3-C from the Steve Jobs audit. The internal design review proposed adding skeleton loaders to the Licenses-table Sites column during a per-row AJAX domain-count load. On code inspection, the Sites count is rendered server-side from the `registered_domains` DB column at page-render time, NOT via AJAX — so there's no AJAX call to add a skeleton to. P3-C was based on a wrong reading of the code and is dropped from this ship. (Audit fidelity lesson: verify the assumption when you go to ship.)
  • Copy-button confirmation flash on the SDK Generator's post-submit Step 1 + Step 2 code blocks (P1-E sub-item) is auto-handled via the existing `.cls-copy-btn` class wired in `assets/admin.js` — no code change needed in this ship, just verified the flash animation triggers correctly on the generator's output buttons.
Bodholdt Licensing v10.14.0
  • NEW: Customer Portal Steve Jobs polish — verify-step echoes back your email. Pre-v10.14.0, when a customer clicked "My Keys" on the Manage Subscription portal page, the generic prompt *"Enter the 6-digit code sent to your email"* appeared with no echo-back of which email the code was actually sent to. A customer who typed their email wrong didn't realize until they sat waiting for a code that never arrived. v10.14.0 rewrites the prompt to read *"We sent a 6-digit code to [email protected] — check your inbox!"* (the bold email is inserted via DOM `createTextNode` so customer-supplied text can't inject markup). Also adds a "Wrong email?" affordance that re-shows the email input on click, in case the customer notices the mistake from the echo-back. Refreshes the same echo-back when the customer clicks "Resend Code", so support tickets for "I never got the email" become diagnose-yourself.
  • NEW: Customer Portal Steve Jobs polish — skeleton loaders during the verify-code AJAX. Pre-v10.14.0, after submitting the 6-digit code, the customer waited 1-4 seconds (depending on connection + server) staring at a button that just said "Verifying..." with no visual progress in the results area. v10.14.0 renders 3 shimmering skeleton rows in the results area during the verify AJAX call — same width/height/border-radius as the final license-result rows that replace them. Customer perceives the page as "working on it" instead of "frozen". Uses a single CSS `@keyframes cls-skeleton-shimmer` (200% background-size + linear-gradient → animated background-position) — no external dependencies, ~12 lines of CSS, 0 JS frameworks. Skeleton rows are `aria-hidden="true"` so screen readers don't announce three empty regions during the wait. On AJAX failure, skeletons are cleared so the error message stands alone.
  • Note on version-number gap (10.5 → 10.14.0): the plugin has shipped many intermediate versions (10.11.x, 10.12.x, 10.13.0 — multisite license-storage migration) since the last readme.txt update. Those entries were structurally shipped via the version constant + plugin header. v10.14.0 catches up the readme to current. Future versions append here normally.
Bodholdt Licensing v10.5
  • AUDIT: Comprehensive 9-phase commercial-grade self-audit
  • SECURITY: 32 fixes — sanitized all superglobals, escaped all output
  • SECURITY: Replaced date() with wp_date(), json_encode() with wp_json_encode()
  • SECURITY: Added sanitize_callback to all 18 register_setting() calls
  • SECURITY: Replaced short echo tags for PHP short_open_tag compatibility
  • SECURITY: License key no longer exposed in JavaScript (boolean flag only)
  • NEW: "Preserve data on delete" option in Settings > Advanced
  • NEW: Contextual help tabs on all admin screens
  • IMPROVED: Dashboard query caching with 5-minute transient
  • IMPROVED: autoload=false on infrequently-accessed options
  • IMPROVED: PHP 8.0 runtime version check on activation
Bodholdt Licensing v10.0
  • REFACTORED: Monolithic admin class (2,291 lines) split into 7 focused modules
  • NEW: includes/admin/ directory with dedicated page files for each admin screen
  • NEW: Admin bootstrap dispatcher (184 lines)
  • IMPROVED: Inline styles moved to admin.css (Content Security Policy ready)
  • IMPROVED: Brand header and version tag use proper CSS classes
Bodholdt Licensing v9.0
  • IMPROVED: Full WordPress Coding Standards compliance
  • NEW: i18n-ready with complete .pot file for translations
  • NEW: GPL-2.0-or-later LICENSE file included in distribution
  • NEW: Distribution build script (build.sh) with critical file verification
  • NEW: Directory browsing guards (index.php) on all directories
  • IMPROVED: cls_format_license_key() centralized in helpers.php
Bodholdt Licensing v8.1
  • SECURITY: Fixed grace period loophole — 7-day activation deadline enforced
  • SECURITY: Settings import can no longer override operating mode
  • SECURITY: Stripe secret keys excluded from settings export
  • SECURITY: REST API GET /licenses/{key} now returns minimal data for public requests
  • SECURITY: License key lookup now requires email verification (6-digit code)
  • SECURITY: Removed @unserialize fallback (all data migrated to JSON)
  • SECURITY: X-Forwarded-For header no longer trusted (Cloudflare IP only)
  • SECURITY: .htaccess protection for download storage directory
  • NEW: max_allowed_domains configurable per product in Product Manager
  • NEW: invoice.payment_failed webhook handler with activity logging
  • NEW: Production-quality SDK Generator with auto-updater, deactivation, and feature gating
  • IMPROVED: Consolidated API init hooks into single dispatcher
  • IMPROVED: Email delivery failures logged to activity log
  • IMPROVED: Updated README documentation for v8.x architecture
  • FIX: Version alignment across all files (8.1)
  • FIX: .DS_Store files removed
Bodholdt Licensing v8.0
  • NEW: Licensed Self-Hosted operating mode (phone-home verification)
  • NEW: Soft lockout system — only blocks new license creation on expired license
  • NEW: Auto-updates from Bodholdt home server
  • NEW: Simplified admin UI — single operating mode, clean license key section
  • NEW: Self-referential licensing (server licenses itself)
  • IMPROVED: Operating mode radio buttons removed from UI
Bodholdt Licensing v7.0
  • NEW: Self-Hosted / Client dual operating mode
  • NEW: Auto-updater system for client installations
  • NEW: Activity logging with audit trail
  • NEW: Settings export/import
  • NEW: Bulk license operations
  • NEW: REST API endpoints
  • NEW: Configurable currency
  • NEW: Configurable page slugs
  • NEW: Webhook URL auto-display
  • NEW: Admin notification system
  • NEW: Stripe version conflict detection
  • NEW: Multi-file plugin architecture
  • NEW: WordPress-standard README and documentation
  • NEW: i18n text domain support
  • NEW: uninstall.php for clean removal
  • NEW: Deactivation hook for transient cleanup
  • NEW: PHPUnit test stubs
  • IMPROVED: Gatekeeper bypassed in self-hosted mode (no chicken-and-egg problem)
  • IMPROVED: All page slug references use saved options
  • 20 commercial readiness improvements