Launch pricing: Lock in Pro at $79/yr before prices go up View pricing →

Changelog

June 13, 2026

Release: v1.2.2

1.2.2

  • The one-click Install & activate the free base plugin button now falls back to the publisher-hosted download (sellinor.dev) when WordPress.org doesn’t yet have the plugin (e.g. during the review window) or its API is unreachable. WordPress.org stays the preferred source once the listing is live. Filterable via edfw_pro_free_fallback_download_url (HTTPS only).
  • Lockstep maintenance bump; no functional changes.

June 12, 2026

Releases: v1.1.13 – v1.2.1

1.2.1

  • Added (Pro): Seamless onboarding for buyers who install Pro first — Pro now activates without the free base plugin (staying safely dormant), shows a single notice explaining the free/Pro relationship, and offers a one-click Install & activate the free base plugin button that finishes setup and lands on the License screen.
  • Changed (Pro): Dropped Requires Plugins from the Pro header — WordPress 6.5+ enforced it with a hard activation block before the guided flow could run; the plugin’s own runtime guards and the new flow handle every WordPress version consistently.

1.2.0

  • Added: Theme-proof on-product display — when a customized theme doesn’t fire the standard product summary hook, the expiration date now renders via the add-to-cart form area instead (never duplicated on standard themes).
  • Added: An [edfw_expiration_date] shortcode for manual placement in page builders and fully bespoke templates (optional id attribute; suppresses the automatic placement on that product’s page; works even with the display toggle off).
  • Improved: The “Show on product page” setting now explains placement and points to the shortcode for heavily customized themes.

1.1.15

  • Improved: Consistent wording across the Pro hints — every mention now says “the Pro add-on” instead of a bare “Pro” as the sentence subject (product editor, Overview hint, Import/Export note, Pro Features page).

1.1.14

  • Improved: Clearer wording on the Overview email hint (“Get this report by email — Pro sends you an expiring-products digest daily, weekly, or monthly.”).

1.1.13

  • Performance: Overview statistics are cached and the inventory-value calculation is bounded; the at-risk query no longer depends on a relaxed MySQL mode (works under ONLY_FULL_GROUP_BY).
  • Security: the CSV importer inspects file content (not just the extension) and rejects non-CSV uploads.
  • UX: drag-and-drop CSV staging (import runs on click, never on drop), post/redirect/get for “Clear all logs”, equal-width segmented settings tabs verified across Chromium/WebKit/Firefox, inline-SVG button icons (no icon-font alignment drift), flex page header immune to other plugins’ admin CSS, centered help tips, uniform Upgrade-button sizing.
  • Upsell surfaces: Pro Features tab with price/trial line, locked Notifications/Discounts preview panels, dismissible Overview hint, product-editor and Import/Export one-liners.
  • License: 3-day post-expiry grace with immediate revalidation (renewed customers never lose features to a missed webhook), hourly back-off when the license server is unreachable, “Pro license inactive” banners replace silently-paused settings tabs, webhook signing secret manageable via UI/option/constant (hidden on customer installs).
  • Batches: orders held for insufficient batch stock re-run FEFO automatically on resume; resume reconciliation is refund-aware (no phantom deductions after partial refunds); adding a batch enables tracking on demand (no product save required first); the manual date field hides while batches derive the product date; deadlock detection covers every step of the deduction transaction.
  • Notifications: digest query bounded and ordered soonest-expiring-first; plain-text emails no longer show HTML entities; test email no longer registers duplicate hooks.

June 9, 2026

Releases: v1.0.94 – v1.0.95

1.0.95

  • Admin polish: The “Site timezone” line in the plugin status panel now reads correctly (e.g. “UTC” or “America/New_York”) instead of a doubled “UTCUTC”, and the “expiring soon threshold” status now reads “1 day” instead of “1 days”.

1.0.94

  • Block-theme support for the on-product expiration display (Free): The expiration date now also shows on the single product page for block themes that use WooCommerce’s block-based product template (for example the default Twenty Twenty-Four / Twenty Twenty-Five themes), appearing right after the product price. Previously this on-product badge only rendered on classic WooCommerce themes that fire the standard product hooks. Classic themes are unchanged, and the badge is shown exactly once with no duplication.

June 7, 2026

Releases: v1.0.86 – v1.0.93

1.0.93

  • Custom discount badge text (Pro): A discounted simple product’s badge now shows your configured “Badge text” (e.g. “20% off - Clearance”) instead of always reading “Expiring soon” — matching how variable products already render it. Installs that kept the default text see no change.
  • Dashboard widget hardening (Free): The “Expiring products” dashboard widget is now shown only to users who can manage WooCommerce, so other admin roles on a mixed site can no longer see product stock levels.
  • Tidier import errors (Free): An invalid date value in a CSV import error notice is truncated, so one malformed row can’t bloat the message.
  • Maintenance: Removed dead admin CSS that shipped in the Free zip and a couple of small internal cleanups.

1.0.92

  • Translation fix (Free): The “Clear all logs” confirmation prompt now works correctly in translated languages whose text contains an apostrophe (previously, e.g. in French, the confirmation dialog could be skipped). No security impact — the server still verifies the request — but the safety prompt is restored.
  • Safer batch dates (Pro): A batch whose stored expiration date is somehow corrupt now shows “unknown date” instead of a wrong, rolled-over date.
  • Accurate variation discounts (Pro): Changing a variable product’s default expiration date now correctly refreshes the discounts on its variations within the same request.
  • Accessibility (Free): The active tab and the active settings section are now announced to screen readers (aria-current).
  • Consistency (Free): The dashboard widget skips impossible stored dates (matching the product list), and the activity-log statistics now include batch reconciliations.

1.0.91

  • Consistent date validity (Free): The System Info report and the dashboard “with dates” / “expired” counts now ignore impossible stored dates (e.g. a corrupt “2025-02-29”), so every screen agrees on what counts as a real expiration date.
  • Safer variation display (Free): A variation whose stored date is corrupt now shows no date rather than a wrong one on the product page, consistent with the rest of the plugin.
  • Robust filtered views (Free): A crafted, unparseable date in a report filter link now shows the raw value instead of silently labeling it as today.
  • Safe stock restore (Pro): Stock restored from a cancelled order is accounted for safely even in the rare case a batch credit fails mid-restore.
  • Internationalization: Refreshed the translation template (now covering 48 strings that had drifted out of date) and fixed a batch-notice that wasn’t showing its label.
  • Maintenance: Removed dead code and corrected a misleading code comment in the CSV upload handler.

1.0.90

  • License renewal sync (Pro): The weekly license check now reliably performs its authoritative re-validation against the license server again, so a renewed, changed, or cancelled subscription is always reconciled correctly.
  • Legacy upgrade migration: Upgrading from a much older version now reliably carries a previously enabled “automatically hide expired products” setting over to the current expiry-action control.
  • Deterministic batch deduction (Pro): When several batches of the same product share an expiration date, stock is now always drawn from the oldest-created batch first, in a stable and predictable order.
  • Maintenance: Documentation, internationalization, and accessibility polish across the plugin, with no change to existing behavior.

1.0.89

  • Discount badge styling (Pro): the expiring-soon badge now displays correctly even when the on-product date display is turned off.
  • Privacy in logs (Pro): webhook debug logging no longer records the license key or buyer email.
  • Type-change cleanup (Free): converting a variable product to a simple product clears its leftover synced date, so it no longer shows a date it doesn’t have.
  • Robustness: corrupt batch dates are handled defensively, import error lists are capped, the swap re-fetch is null-guarded, and a number field now enforces its documented maximum.
  • Housekeeping: accessibility, internationalization, consistency, and security-log refinements; removed dead code and consolidated duplicated logic.

1.0.88

  • Correct batch calendar (Pro): the calendar now computes the right end-of-month on servers whose timezone differs from the site timezone.
  • Accurate refunds (Pro): refund restocking no longer mis-targets a duplicate line item when an order contains the same product on more than one line.
  • Cleaner display (Free): the dashboard widget reflects a product’s real stock status, the calendar’s weekday initials translate correctly per day, and a corrupt stored date can never render as a wrong date.
  • Security & robustness (Pro): the batch edit dialog is hardened against malformed batch numbers, batch creation verifies tracking is enabled, and export filenames are sanitized.
  • Accessibility, i18n & housekeeping: translatable strings, scoped styles, corrected documentation, and removed dead/duplicated code.

1.0.87

  • Accurate dashboard (Free): report counts and the calendar load more efficiently and consistently exclude impossible stored dates; product names render correctly in the Recent Activity widget.
  • Safer Pro inventory (Pro): batch actions verify per-product edit permission, multisite stock-sync locks are isolated per site, and the minimum required Free version is now enforced so a version mismatch can’t cause an error.
  • Resilience: a corrupt stored date can no longer produce a wrong reformatted date or an empty badge; notification recipients and settings values are validated and clamped defensively.
  • Accessibility & housekeeping: screen-reader-announced toasts, a fixed “Variation” tag style, security log-hygiene, corrected documentation, and removed dead code.

1.0.86

  • More resilient dates (Free & Pro): corrupt or invalid stored expiration dates are now handled defensively everywhere, so they can never trigger a PHP error on a product or pricing page.
  • Activity Log timezone (Free): the Activity Log table now shows timestamps in your site’s timezone, matching the recent-activity widget.
  • Validated notification recipients (Pro): recipient email addresses are checked when you save settings, so a typo can’t silently break notifications.
  • Security hardening: the license key is no longer echoed to the browser, admin messages render as plain text (not HTML), CSV uploads are verified before processing, and more.
  • Accessibility & i18n: keyboard-operable reports calendar, proper table-header semantics, focus return on dialogs, and localized strings replacing hardcoded text.
  • Housekeeping: removed unused code and consolidated duplicated logic, with no change to existing behavior.

June 6, 2026

Releases: v1.0.66 – v1.0.85

1.0.85

  • Changelog housekeeping (no functional changes): Shortened the on-page changelog to the most recent versions so it renders fully within WordPress.org’s display limit. The plugin’s behaviour is identical to 1.0.84.

1.0.84

  • Resilient discount settings (Pro): Hardened the expiry-discount tiers against a discount setting that was corrupted outside the plugin (e.g. a manual database edit or a faulty migration). A malformed tier is now safely ignored instead of risking a PHP error on product pages or applying an incorrect discount.

1.0.83

  • On-backorder status preserved (Free): Un-expiring a product — by extending its date or turning the expiry action off — now restores its exact previous stock status. A product deliberately set to “On backorder” is no longer silently switched to “In stock”.
  • Accurate dashboard counts (Free): The “Expired” and “Expiring soon” tiles no longer count products whose stored date is impossible (e.g. a corrupt “2025-02-29”), so the numbers match the lists they link to.
  • Cleaner CSV import (Free): Importing a CSV whose first line is blank no longer produces a PHP notice; the file is still rejected with a clear “missing Expiration date column” message.

1.0.82

  • Padded textual dates in CSV import (Free): The importer now also accepts spelled-out dates that use a leading-zero day — “March 02, 2027”, “02 March 2027”, “Mar 02 2027” and similar. Numeric and non-padded textual dates already worked; impossible or malformed dates are still rejected.
  • Symmetric uninstall cleanup (Pro): With “delete all data on uninstall” enabled, deleting the Pro add-on after the Free plugin has already been removed now also clears the Free plugin’s leftover activity-log table and options. Combined with the v1.0.81 Free-side backstop, a full uninstall in either order now leaves nothing behind.

1.0.81

  • Fully-expired variable products show out of stock (Free): A variable product that manages stock at the parent level now correctly reads as out of stock on the storefront once all of its variations have expired (when the out-of-stock action is enabled). Such products previously kept showing “in stock” even though none of their variations could be purchased. Applied at display time, so extending a date restores the product with nothing left stranded.
  • Thorough uninstall backstop (Free): With “delete all data on uninstall” enabled, removing the Free plugin after the Pro add-on has already been deleted now also clears the Pro batch inventory tables and their order references, so a full uninstall leaves nothing behind.

1.0.80

  • Order editor — batch quantity increase (Pro): Raising a batch-tracked line item’s quantity, or adding a new batch-tracked line, on an already-reduced order now deducts the extra units from your batch inventory (earliest-expiring first) and re-syncs WooCommerce stock from the batch totals. Previously the stock count was lowered but the batches were left untouched, so the next inventory sync could resurrect the sold units and cause overselling.
  • Expiry discounts on batch products (Pro): A batch-tracked product near its earliest lot’s date no longer has its clearance discount hidden. Because later lots keep it sellable, the discount now shows correctly instead of being suppressed by the single-date expiry rule.
  • Report total count (Free): The expiration report’s total no longer counts products whose stored date is empty or corrupt, so the number above the list matches the rows actually listed.

1.0.79

  • Fixed (Pro): Reducing or removing a batch-tracked line item in the admin order editor (or via REST) now credits the freed units back to the batch ledger. Like the v1.0.78 single-date fix, the order editor restocks through wc_maybe_adjust_line_item_product_stock(), which raises WooCommerce’s mirrored stock directly and fires none of the refund/cancel hooks — so the batches were never credited and the units were lost on the next sync_wc_stock (oversell). New woocommerce_before_delete_order_item / woocommerce_saved_order_items handlers fold them back (full on remove, reverse-FEFO partial on reduce), idempotent via the allocation meta, and never restock on a bulk order force-delete.
  • Fixed (Pro): The built-in WooCommerce CSV importer no longer wipes a product’s existing expiration date when a cell holds an invalid/unparseable date — the cell is skipped (date preserved), matching the plugin’s own importer. An empty cell still clears; a valid date still saves.

1.0.78

  • Fixed (Free): An admin order-editor restock — reducing or removing a line item on an existing order, via wc_maybe_adjust_line_item_product_stock() — is a third order-restock path that, unlike a refund or cancellation, fires neither item-restock hook. For an expired, swept, stock-managed product the expiry system was overwriting the captured pre-expiry stock baseline with the small restocked quantity instead of folding it additively, so on un-expiry the merchant got back only the restocked amount and the rest of the on-hand was silently lost. is_admin_order_restock_in_progress() now detects this path and folds the returned units into the baseline, matching the refund/cancel handlers. Manual stock edits still overwrite; refund/cancel still fold (unchanged). Verified end-to-end.

1.0.77

  • Maintenance (Free): Removed assets/js/reports.js — a 118-line script that shipped in the zip but was never enqueued (only dashboard.js loads for the Overview tab). It bound to a “tile options” UI that exists nowhere in the rendered markup and fired an AJAX action with no registered handler. Verified zero remaining references repo-wide. dashboard.js (live calendar navigation) and reports.css (the Overview edfw-dashboard style) are unchanged. No functional change.

1.0.76

  • Maintenance (Pro): Defensive hardening in the batch inventory data layer. Batch_Repository’s public ID-taking methods — delete(), delete_product_batches(), update_quantity(), deduct_quantity(), add_quantity() — now reject a non-numeric argument (e.g. a Batch object accidentally passed where its numeric ID was expected) with an is_numeric() guard before absint(), failing safe (false/0) with no wpdb::prepare notice. No current caller hits this; valid integer IDs behave exactly as before. The FREE plugin is unchanged.

1.0.75

  • Maintenance (Free): Satisfy the WordPress.org Plugin Check coding standard (WordPress.WP.AlternativeFunctions.file_system_operations_fread) for the CSV importer’s byte-order-mark detection. Line-by-line CSV parsing needs a real fopen() stream for fgetcsv() (WP_Filesystem has no streaming API) and we only peek the first 3 bytes to skip a leading UTF-8 BOM — so this is a justified phpcs:ignore matching the surrounding fopen/fclose calls. Annotation only — no functional or behavioral change.

1.0.74

  • Fixed (Free): With the hide action enabled, expired products are now also excluded from WooCommerce Store API product listings — the Products, Product Collection, and All Products blocks that are the default WP 7.0 / WC 10.8 storefront. The classic woocommerce_product_query path didn’t cover the Store API’s own WP_Query; a tightly route-gated pre_get_posts filter now does. (Purchase of expired products was already blocked everywhere by the purchasable filters; this closes the display leak.)
  • Fixed (Free): fold_order_restock_into_baseline() now carries the same parent-managed-variation guard as set_product_out_of_stock(), so a refund/cancel restock of an expired parent-managed variation no longer writes dead, self-reverting stock meta onto the variation row.
  • Fixed (Free): When an add-on claims a product via edfw_skip_single_date_expiry_check, the sweep now clears the stale MAX_META_KEY it can never satisfy, so the hourly cron stops re-selecting it every run.

1.0.73

  • Fixed (Free): The hourly single-date out-of-stock sweep now honors the edfw_skip_single_date_expiry_check opt-out, so a product moved from a single date to Pro batch tracking is no longer zeroed from its stale MAX_META_KEY. Previously the sweep would hide a product that has sellable, non-expired batches (Pro never updates MAX_META_KEY), overriding FEFO.
  • Fixed (Free): A dateless variation’s front-end expiry fallback now uses the parent’s inherited default date instead of the synced earliest variation date, so it no longer displays a sibling variation’s date or a false “expired” badge.
  • Fixed (Pro): The expiration digest now also matches variable parents by their inherited default date, so inheriting variations are included even when a self-dated sibling has already expired and pushed the parent’s earliest date out of the window.
  • Fixed (Pro): A keyless subscription webhook (attributed only by buyer email, which can be shared across multiple subscriptions) no longer downgrades this license directly — it schedules an authoritative re-validation against Lemon Squeezy instead.
  • Fixed (Pro): “Delete all data” on uninstall now also removes the _edfw_batch_allocation order-item meta.

1.0.72

  • Fixed (Free): parse_date_strictly() rewritten to parse textual dates against an explicit format allow-list with a strict round-trip, replacing the permissive strtotime() fallback. It now rejects mangled/non-English month names (2 Marz 2027 was silently stored as the current-year March 2), relative modifiers appended to a real date (2 March 2027 next year), and calendar rollovers — and now accepts ordinals (3rd March 2027).
  • Fixed (Pro): display_batch_in_email() now matches WooCommerce’s 4-argument woocommerce_order_item_meta_start signature. The plain-text flag is the 4th argument; the 3-arg callback bound it to the order object (always truthy), so HTML order emails always rendered the plain-text batch layout.
  • Fixed (Pro): The expiration digest now expands inherited-default variations into their own rows, so a variable product with a mix of self-dated and inherited-default variations no longer drops the inheriting ones.
  • Fixed (Pro): pro/uninstall.php moves edfw_pro_batch_reconcile_since to the EDFW_DELETE_ALL_DATA-gated group. It is the oversell guard tied to the batch tables, so on the preserve path it must survive — deleting it let a delete-then-reinstall skip the reconcile and oversell.

1.0.71

  • Fixed (Free): Expired variations of a variable product that manages stock at the parent level no longer write a self-reverting out-of-stock status or leave an orphan _stock=0. WooCommerce returns 'parent' (not a bool) from get_manage_stock() for these variations and rewrites their status on the next parent save; the sweep now records its progress flag and relies on the date-based purchasable filter, which already blocks the sale.
  • Fixed (Free): CSV import rejects ambiguous relative/partial dates (today, next thursday, +3 days, Mar 2027) instead of letting strtotime() store a run-time-dependent date. Fully-qualified dates (2 March 2027) still import.
  • Fixed (Free): CSV read/write now uses escape='' (RFC 4180), so a value ending in a backslash (e.g. a SKU) round-trips correctly and no longer swallows the following row.
  • Fixed (Pro): The expiration email digest now includes variable products whose variations inherit the product’s default date (the parent row is the only carrier of that date), matching the Reports behaviour.

1.0.70

  • Fixed (Free): External/affiliate products that carry an expiration date no longer fatal the scheduled expiry sweep. WC_Product_External::set_stock_status('outofstock') throws a WC_Data_Exception (“External products cannot be stock managed”); the sweep now records its forward-progress flag and skips these products — an expired product is already blocked from purchase by its date.
  • Fixed (Pro): The batch-swap actions on the order edit screen now require an active Pro license, matching the rest of the batch tooling. The edfw_swap_batch / edfw_get_swap_batches AJAX endpoints previously ran with only a nonce + edit_shop_orders capability check.
  • Fixed (Pro): Removing the Pro add-on now preserves merchant-configured settings (discount tiers, batch-selling behaviour, email notifications) unless Delete all data on uninstall is enabled — mirroring the preserve-by-default contract already applied to batch inventory tables.

1.0.69

  • Maintenance (Pro): Removed an unused remote-pricing code path (and its outbound request) that no feature called. No user-facing or licensing behavior changes.

1.0.68

  • Fixed: CSV files saved as “CSV UTF-8” (for example from Microsoft Excel) — which begin with a byte-order mark and quote every field — now import correctly. Previously the first column could go undetected, causing the import to fail or be rejected.
  • Fixed (Pro): Saving automatic-discount tiers for the very first time now takes effect immediately within the same request (the price cache is refreshed on the initial save, not only on later edits).

1.0.67

  • Maintenance: Corrected an inaccurate internal code comment in the uninstall routine (no behavior change; the data-preservation logic was already correct).

1.0.66

  • Fixed: The “needs attention” report view’s heading now accurately states that it includes already-expired products (not only those expiring within N days), and uses the correct day count.
  • Fixed (Pro): Importing products through WooCommerce’s CSV importer no longer writes a spurious “Expiration date cleared” activity-log entry for products that never had a date (a blank cell on a no-date product is now correctly a no-op).

June 5, 2026

Release: v1.0.65

1.0.65

  • Fixed (Pro): The weekly license re-check now always reconciles against Lemon Squeezy, so an expiry/renewal date received from a subscription webhook can no longer persist longer than Lemon Squeezy actually reports.

June 4, 2026

Releases: v1.0.63 – v1.0.64

1.0.64

  • Hardened: Detecting whether a stock change comes from an order refund/cancellation no longer depends on a fixed call-stack depth. On sites with many plugins, a deep call stack could previously cause a refund/cancellation restock to be mistaken for a manual stock edit — which could lose stock when an expired product was later un-expired. This edge is now closed.

1.0.63

  • Fixed: The “Pro Features” upgrade page is now also hidden when the Pro add-on is active — opening its URL directly while Pro is installed shows the dashboard instead of the marketing page.

June 3, 2026

Releases: v1.0.58 – v1.0.61

1.0.61

  • New: An “Upgrade to Pro” page inside the plugin that introduces the optional Pro add-on — batch & lot tracking (FEFO), automatic expiry discounts, email alerts & digests, WooCommerce CSV integration, and batch details in orders. The free plugin stays fully functional; these surfaces link to the separately-hosted product page.

1.0.60

  • Fixed (Pro): When two variations of the same variable product sell out at the same instant, updating the parent product’s stock status is now serialized, preventing a harmless but noisy “Duplicate entry” database warning from being logged.

1.0.59

  • Fixed (Pro): A free product (zero or empty price) approaching its expiration date is no longer incorrectly shown as “on sale” with a $0 → $0 strikethrough. Automatic discounts now apply only to products that actually have a price.
  • Fixed (Pro): License expiry is now reconciled strictly against Lemon Squeezy on every scheduled re-check (and an out-of-range expiry date from a subscription event is rejected), so a license can never report a longer validity than Lemon Squeezy actually grants.

1.0.58

  • Fixed (Pro): The automatic-discount settings are now read defensively, so a discount-tier option corrupted outside the plugin’s own settings screen (for example by a faulty import or a manual database edit) degrades gracefully instead of causing an error on storefront product pages.
  • Fixed: Reading the activity log no longer emits a PHP notice on newer PHP versions when a log entry has no extra data.

June 2, 2026

Releases: v1.0.50 – v1.0.57

1.0.57

  • Fixed (Pro): Orders placed while the Pro add-on was deactivated are now reconciled into batch stock on re-activation even when they are in a custom or third-party order status (for example a fulfilment or POS plugin’s own status), not only the standard Processing / On-hold / Completed statuses. Previously such an order’s already-sold units could be added back as sellable stock (overselling).
  • Fixed: The product-list Expiration column no longer shows a nonsensical date for a corrupt or malformed stored value on a simple product (for example 0000-00-00 now shows an em dash), matching how variable products and the Reports screens already handle it.
  • Fixed (Pro): “Batch reconciled” entries in the Activity Log now show a readable label and can be filtered by action, instead of displaying their internal key.

1.0.56

  • Fixed (Pro): On a store with more than 1,000 orders placed while the Pro add-on was deactivated, re-activating it now reconciles every one of those orders into batch stock instead of only the first 1,000. Previously the excess orders were skipped and their already-sold units could be added back as sellable stock (overselling). Reconciliation now also resumes on the next scheduled sync if it is interrupted partway through.
  • Fixed: A corrupt or malformed stored expiration date — for example 0000-00-00 or non-date text from a bad import or a manual database edit — is no longer rendered as a nonsensical date and no longer counts toward the Expired total in Reports or the product list. Such values are now skipped or shown as an em dash.
  • Fixed: A leftover inherited “default” expiration date is now cleared when a variable product is converted to a simple product, so it cannot silently reappear on variations if the product is later converted back to variable.
  • Fixed: The 10,000-row CSV import limit now counts records the way the importer actually reads them, so a single row containing line breaks inside a quoted field is no longer miscounted as several rows.

1.0.55

  • Fixed (Pro): Importing an expiration date through WooCommerce’s own product CSV importer now rejects a calendar-impossible text date — for example 30 Feb 2027 or 31 Apr 2027 — instead of silently shifting it to a different real date. The WooCommerce-native import path now shares the exact same strict date validation as the plugin’s own CSV importer, closing the asymmetry introduced alongside the v1.0.54 fix.

1.0.54

  • Fixed (CSV import): A text-format expiration date with an impossible day — for example 30 Feb 2027 or 31 Apr 2027 — is now reported as an invalid date instead of being silently shifted to a different real date (previously 30 Feb 2027 became 2027-03-02). This makes the textual import path consistent with the numeric one, which already rejected dates like 2027-02-30.
  • Hardened (settings validation): Expiration day-count fields are now clamped to their allowed 1–365 range when saved — the Free “expires soon” frontend window, and Pro’s notification lead time and discount tiers — and the Pro notification schedule only accepts its listed options. A forged or out-of-range save can no longer persist an absurd value.

1.0.53

  • Fixed: A product that was oversold into backorder (more units ordered than were in stock) and then expired no longer over-restores its stock when that order is later refunded or cancelled. The restored quantity now reflects true on-hand (e.g. 3 in stock − 5 sold + 4 returned = 2), preventing phantom inventory / overselling.

1.0.52

  • Improved (Pro): Removed an unreachable “Expired” label branch from the expiration digest email templates. The digest lists only upcoming expirations (today onward), so the branch never rendered — this is an internal consistency cleanup with no change to the emails you receive.

1.0.51

  • Fixed: A product with backorders enabled no longer loses its stock when it expires and is later un-expired — its quantity is correctly restored, exactly like a product without backorders. (While expired, such a product showed an “on backorder” status, which previously prevented the stock from being restored. Expiry/un-expiry happens routinely via the hourly cron, date edits, and settings changes, so this could silently destroy inventory.)
  • Fixed (Pro): The “Test email sent to …” confirmation, and the activity-log entry for the scheduled digest, now report the address the email was actually delivered to when the WooCommerce → Emails “Recipient(s)” field is used.

1.0.50

  • Fixed: An order that lists the same product as more than one line item, placed on a product that had just expired (before the hourly cleanup ran), no longer over-restores stock when that order is later cancelled or refunded — the restored quantity now matches the true on-hand. Single-line orders, partial refunds, and the normal swept path are unchanged.
  • Fixed (Pro): A leftover internal reconcile flag is removed during a full uninstall, so no plugin option is orphaned after both plugins are deleted with “delete all data” enabled.

June 1, 2026

Releases: v1.0.40 – v1.0.49

1.0.49

  • Fixed (Pro): The disable/re-enable stock reconciliation now correctly handles an order placed during the off window for more units than were in stock (for example with backorders enabled). It deducts all available batch stock for that already-shipped order instead of skipping it, so the sold units are not restored as available when the plugin is reactivated. Live (normal) sales keep their all-or-nothing FEFO guarantee and still never oversell.

1.0.48

  • Fixed (Pro): Uninstalling the free plugin while the Pro add-on stays installed no longer discards the pending stock-reconcile marker, so a later reactivation still correctly reconciles orders placed while batch tracking was paused — continuing to prevent overselling. (Closes a gap in the 1.0.46/1.0.47 disable/re-enable hardening.)
  • Fixed: On the Reports “Expired” tab, products that share the same expiration date now appear in a consistent order across pages (previously they could be shown in a zig-zag order). No rows were ever skipped or duplicated.

1.0.47

  • Fixed (Pro): The overselling safeguard added in 1.0.46 now also covers deactivating the free plugin (which also pauses batch tracking, since Pro runs on top of it). Orders placed while the free plugin was inactive are reconciled into your batches promptly on reactivation — within seconds — instead of being restored as available. This completes the disable/re-enable hardening on both deactivation paths.

1.0.46

  • Fixed (Pro): Orders placed while the Pro add-on was deactivated are now reconciled into your batch quantities when Pro is reactivated, so the stock those orders consumed is no longer restored as available (which could otherwise cause an oversell). The reconciliation runs automatically on reactivation and only adjusts batches for genuinely unallocated sales — it never disturbs batch additions or the sell-expired setting.

1.0.45

  • Fixed: Refunding or cancelling an order for a product that had just expired (before the hourly cleanup ran) no longer over-counts the returned units — the product is restored to its true stock, not an inflated amount, when it is no longer expired.
  • Fixed: The regular CSV export now includes a variable product that has only a default expiration date and no dated variations, matching the downloadable template and the export description.

1.0.44

  • Fixed (Pro): Deleting the Pro add-on while the free plugin is only temporarily deactivated (for example when WooCommerce itself is deactivated, which cascade-deactivates the free plugin) no longer erases your product expiration dates. The shared expiration data is now removed only when the free plugin’s files are genuinely being removed too, mirroring the free plugin’s own uninstall logic.
  • Fixed: The CSV importer now reports a calendar-invalid date (such as Feb 30, Apr 31, or month 00) as an error instead of silently saving a different, shifted date.

1.0.43

  • Fixed (Pro): When order emails use the plain-text format, the batch “Lot:” line no longer leaks onto the customer-facing order-received and “View order” pages. The batch line is an email-only detail and now renders only inside order emails, regardless of email format.

1.0.42

  • Fixed: A manual stock edit on an expired product is now recorded reliably even if another plugin’s error interrupts an order cancellation in the same request — the corrected quantity can no longer be silently lost (which could otherwise leave the product sellable for units that don’t exist). The order-restock vs. manual-edit distinction is now derived from the live call stack instead of a request-scoped flag.
  • Fixed (Pro): Swapping an order line’s batch into a batch already used by that same line and then partially refunding it no longer mis-tracks the allocation or loses units from the batch ledger. Swaps also merge into the existing allocation entry instead of creating a duplicate.
  • Fixed (Pro): The WooCommerce → Emails “Recipient(s)” field for the Expiring-products email is now honored when set (it previously ignored the field and used the notification settings).

1.0.41

  • Fixed: Lowering a product’s stock while it is expired (a recount, spoilage write-down, or correcting an earlier over-restock) is now respected — un-expiring the product restores the corrected lower quantity instead of an outdated higher one, so it can never be sold for units that no longer exist. Stock returned from a cancelled order on an expired product is likewise preserved exactly (no lost or duplicated units).

1.0.40

  • Fixed: Refunding more units than a product had on hand when it expired no longer inflates the stock restored when the product is later un-expired. The refunded units are now counted exactly once, so the restored quantity always matches true on-hand inventory (this corrects an edge case in the v1.0.39 refund-restock fix).

May 31, 2026

Releases: v1.0.37 – v1.0.39

1.0.39

  • Fixed (Pro): Batch-tracked products are no longer incorrectly marked out of stock or blocked from purchase when their earliest lot has reached the expiry threshold while later lots still hold sellable stock — stock now follows the true sellable batch total on the product page, in the cart, and at checkout (variations included).
  • Fixed: Refunding or cancelling an order for an expired, stock-managed product no longer loses the returned units; they are restored correctly once the product is no longer expired.
  • Fixed (Pro): Simultaneous stock changes (an admin batch edit at the same instant as an order) can no longer leave WooCommerce stock higher than the real batch total.
  • Fixed: The Reports calendar now counts batch-tracked products on their expiration day, so a day’s badge count matches the click-through list (which shows one row per product, with no parent/variation duplication).
  • Fixed (Pro): Expiry discounts and the on-sale badge no longer appear on a product that the expiry action has already blocked from purchase.
  • Fixed: The Activity Log no longer records a “date set” entry when an invalid date is rejected, and now records date changes made through the CSV importer.

1.0.38

1.0.37

  • Improved (Pro): batch stock syncing now serializes concurrent updates with a per-product database lock instead of skipping a sync when one is already running, so a product’s displayed stock can no longer be left transiently too high under simultaneous orders.
  • Improved (Pro): the hourly maintenance task now reconciles the stock of every batch-tracked product, so any temporary drift in the displayed stock number self-corrects within the hour.

May 30, 2026

Releases: v1.0.12 – v1.0.36

1.0.36

  • Fixed: the Reports “Export” now also includes a variable product that carries only a default expiration date (no per-variation dates) on the Expired and specific-date views, matching the downloadable template and the export panel’s description.

1.0.35

  • Fixed: the “Delete all data on uninstall” option is now available on the settings page (Products → Expirations → Settings). The cleanup logic already existed but had no way to be turned on; the documented control now works.
  • Fixed (Pro): discounted price ranges on variable products now display with the correct tax suffix and on-sale strikethrough, matching WooCommerce’s standard price formatting (the discounted amounts were already correct).

1.0.34

  • Fixed (Pro): a Lemon Squeezy subscription event (cancellation/expiration) for a different customer’s license of the same product could downgrade this site’s Pro license to Free. Subscription events are now attributed to this site’s own purchase (by buyer email) and ignored when they can’t be matched, so only your own subscription can change your license status.
  • Fixed (Pro): the “Send test email” preview now shows the correct total and “and N more” notice when there are more expiring products than the email’s display limit, matching the real scheduled email.
  • Fixed: the activity-log entry for a bulk edit of a variable product now records the correct previous date (the inherited default it actually changes) rather than the earliest variation date.

1.0.33

  • Fixed: filtered CSV exports (e.g. “Expiring Soon”) no longer include a variable product whose default date falls outside the selected window — the exported parent row is now matched against the same date filter shown on screen.
  • Improved: clarified the export panel description so it explains that variable products export one row per dated variation, plus a parent row when a default date is set.
  • Fixed (Pro): partial-refund batch stock restore now reduces the remaining allocation by the quantity actually returned to each batch rather than the amount requested, so units aren’t lost when a batch is already at its starting quantity.

1.0.32

  • Fixed: variable products dated only through the inherited default date were missing from the Reports table, statistics, calendar and dashboard widget. Reporting now shows one row per variable product (its earliest variation/default date), so they’re counted — and own-dated variable products appear once instead of once per variation.

1.0.31

  • Fixed: saving a variable product whose variations are only partially expired no longer forces the whole product out of stock; it goes out of stock only once ALL variations have expired.
  • Fixed: the one-time upgrade back-fill now processes products in batches, so it can’t run out of memory on very large catalogs.
  • Fixed: the Reports “Export” button matches its tab — out-of-stock products are excluded for the same tabs that hide them on screen.

1.0.30

  • Fixed: changing a single variation’s expiration date (e.g. via CSV or REST) now brings the variable parent back in stock if it had been fully expired, instead of leaving it stranded out of stock.
  • Fixed: a variable product whose expiration is set only through the inherited default date is now included in CSV export and round-trips correctly on import.
  • Fixed (Pro): batch-tracked products have their stock recalculated as soon as batches cross the “days before” threshold, not only at full expiry, so the displayed stock stays accurate.
  • Fixed (Pro): the discounted price range on variable products now respects your tax display setting on tax-inclusive stores.
  • Fixed (Pro): the “Send test email” button reports a failure if the email could not be sent, instead of always reporting success.

1.0.29

  • Internal: code-quality annotations on the Reports query (from the 1.0.27 sort change). No functional change.

1.0.28

  • New (Pro): a “Send test email” button on the Notifications settings sends a sample digest to your configured recipients to confirm email delivery.
  • Improved (Pro): batch stock synchronization serializes concurrent updates with a database lock, so the WooCommerce stock figure always settles on the latest batch totals under simultaneous orders.

1.0.27

  • Fixed: the Reports product table and the sortable product-list expiration column now paginate deterministically when several products share the same date (stable secondary sort by product ID).
  • Docs: clarified that cart/checkout protection applies when an expiry action is enabled (not in the default “Do nothing” mode).

1.0.26

  • Fixed: cancelling/refunding an order for an expired stock-managed product no longer loses inventory — the saved pre-expiry quantity is preserved (highest known value kept) and fully restored when the product is back in date.
  • Fixed (Pro): a manual sale price of exactly 0 (free) is honored instead of being overridden by the automatic expiry-discount price.
  • Fixed (Pro): the expiring-products email digest no longer lists variations whose parent product is unpublished.

1.0.25

  • Fixed (Pro): exporting/importing products via WooCommerce’s built-in product CSV no longer corrupts a variable product’s inherited default expiration date (variable parents are skipped on both ends; per-variation dates still round-trip).
  • Fixed (Pro): refunding/cancelling an order whose batches were since deleted now reconciles the product’s stock back to the true batch total instead of leaving an over-increment.
  • Docs: corrected an Activity Log filter example that referenced a non-existent “discount changes” option.

1.0.24

  • Fixed (Pro): changing a single variation’s expiration date now refreshes WooCommerce’s cached variation price range, so block/Store-API catalogs show the correct (discounted) price immediately. (Completes the 1.0.23 fix, which only covered the inherited default date.)
  • Fixed (Pro): editing a batch now validates the expiration date (matching the add form) before saving.
  • Fixed: the Reports “value at risk” highlight now uses your configured expiring-soon threshold instead of a fixed 30 days.
  • Fixed: the expiration calendar now counts every product in the month on large catalogs (per-day totals were previously capped at 500).
  • Hardened (Pro): a Lemon Squeezy webhook carrying a license key is ignored unless it matches this site’s license.
  • Docs: corrected the CSV-export product-type list and removed an inaccurate note that discount changes are logged to the Activity Log.

1.0.23

  • Fixed: changing a variable product’s inherited default expiration date (including via CSV import) now refreshes WooCommerce’s cached variation price range, so block/Store-API catalogs show the correct (discounted) Pro prices immediately instead of after the next day.
  • Fixed (Pro): the Pro add-on now declares High-Performance Order Storage (HPOS) compatibility, so WooCommerce no longer flags it as incompatible.

1.0.22

  • Fixed: turning off the automatic expiry action, or lowering the “days before” threshold, now restores products that were previously set out of stock and no longer qualify — instead of leaving them stranded out of stock until each one is edited.
  • Fixed: the Reports expiration calendar no longer errors if an out-of-range month or year is passed in the URL.
  • Fixed: a CSV export of up to 10,000 products can now be re-imported — the import row limit now counts data rows (excluding the header).

1.0.21

  • Fixed: using Quick Edit on a variable product no longer overwrites its inherited “default” expiration date with the earliest variation’s date. The default is edited from the full product editor; Quick Edit now leaves variable products untouched.
  • Fixed: page 2 and beyond of a date-filtered Reports view (a specific calendar day or a date range) now shows the correct products — the date filter was being dropped from the pagination links.
  • Fixed: re-importing a CSV whose SKU legitimately begins with an apostrophe now matches the product (the apostrophe is only stripped as a fallback for export’s formula-injection guard).
  • Fixed (Pro): clearing all automatic-discount tiers now correctly disables discounting instead of silently falling back to the built-in default tiers (14d/10%, 7d/25%, 3d/50%), which could discount products you never intended.

1.0.20

  • Fixed: deleting only the free plugin while keeping the Pro add-on installed could erase the Pro license and batch settings, because the free uninstaller looked for Pro at the wrong location. It now detects the installed Pro add-on correctly and preserves all Pro data.
  • Fixed: the dashboard “Expiring products” widget no longer lists variations whose parent product is not published (matching the Reports page).
  • Fixed: the “Expired <date>” badge on the Reports page now shows the correct date on stores in negative-UTC timezones.

1.0.19

  • Fixed: a product set out of stock on expiry and then re-stocked through a non-editor path (an order cancellation/refund, the REST API, or a manual stock change) is now immediately set back out of stock while still expired, instead of incorrectly showing in stock until its next edit.
  • Performance: the hourly check for expired variations of variable products now runs in pages and skips future-dated products, avoiding a slow unbounded query on large catalogs.

1.0.18

  • Fixed: clicking a day in the expiration calendar now lists exactly the products its count reflects (the calendar counts out-of-stock products on every day; the click-through list now does too).
  • Fixed: exporting a catalog with more than 200 dated products can no longer drop or duplicate a row when products share the same date, by sorting the export by a stable key.
  • Fixed: expiration dates must fall within the years 2000–2100 everywhere they are set, matching the importer, so any saved/exported date can always be re-imported.
  • Fixed (Pro): a second refund processed in the same request can no longer act on the wrong order line item.
  • Fixed (Pro): the single-product “X% off – Expiring soon” badge now only shows when the expiry discount is actually the active price (a deeper manual sale no longer triggers a misleading percentage).
  • Fixed (Pro): the expiring-products email no longer undercounts when more than 500 products fall within the notification window.

1.0.17

  • Fixed: the expiration calendar on the Reports page could render empty or with the wrong day alignment on sites set to a negative-UTC timezone; the month grid and per-day counts are now computed in your site’s timezone.
  • Fixed: a product manually re-stocked while it was still expired now has that quantity restored (instead of the older pre-expiry amount) when you extend or clear its date.
  • Fixed: the frontend “expired” label now appears when a product actually becomes unavailable (at the expiry-action threshold), matching when it is blocked from purchase, instead of only once the date has fully passed.
  • Fixed: clicking a past day in the expiration calendar now lists the same products the day’s count reflects, including expired items that are out of stock.
  • Fixed (Pro): choosing “delete all data on uninstall” and removing the plugins now also clears the per-product batch-tracking flags.

1.0.16

  • Fixed: extending or clearing a variable product’s default expiration date now restores its dateless (inherited-date) variations from out of stock; previously they could remain unsellable.
  • Fixed: the dashboard “Expiring products” widget no longer shows empty when 30 or more already-expired products exist; it reliably lists upcoming products again.
  • Fixed: the Reports product count and pagination no longer overcount variations whose parent product is not published.
  • Fixed: deleting only the free plugin while keeping the Pro add-on installed no longer wipes Pro’s license and settings when “delete all data on uninstall” is enabled.
  • Fixed (Pro): variable-product expiry discounts now apply in block / Store-API catalog grids (the variation price range), which previously could show the undiscounted range while the product page showed the discount.
  • Fixed (Pro): the expiring-products email digest no longer double-lists and double-counts variable products.
  • Fixed (Pro): when a digest covers more than 200 products, an “and N more” line is shown so the email body total matches the subject.

1.0.15

  • Fixed: products set to out of stock on expiry that do NOT use WooCommerce stock management now correctly return to in stock when you extend or clear the date.
  • Fixed: the Reports table, statistic tiles and calendar no longer double-count variable products.
  • Fixed: the product-list “expiring soon” highlight now uses your configured threshold instead of a fixed 30 days.
  • Fixed: deleting a product variation now recomputes the parent product’s earliest and “fully expired” dates.
  • Fixed: importing a variable product’s default expiration date via CSV is no longer wrongly skipped, and re-importing a row whose SKU begins with a special character now matches correctly.
  • Fixed (Pro): if restoring batch stock during a refund fails, the batch allocation record is kept for retry instead of being discarded, preventing permanent stock loss.
  • Fixed (Pro): the Pro add-on now correctly requires the free plugin version 1.0.7 or newer.
  • Fixed (Pro): the expiring-products email digest is no longer silently suppressed by the individual email toggle, and a daily digest can no longer skip a day due to cron timing jitter.
  • Housekeeping (Pro): removed a leftover internal option on uninstall.

1.0.14

  • Fixed (Pro): hardened the 1.0.13 batch-edit safeguard. If the edit form’s original quantity can’t be confirmed (for example a browser tab opened before this update), a quantity change is now skipped rather than risk overwriting a sale that happened in the meantime; the date, batch number and notes still save, and you can reload the page to change the quantity.
  • Fixed: the CSV export now includes grouped products that have their own expiration date. 1.0.13 inadvertently excluded them along with variable parents; grouped products store and re-import their date the same way simple products do, so they belong in the export.

1.0.13

  • Fixed (Pro): editing a batch’s notes, date or number in the admin could overwrite the batch quantity with a value read a moment earlier, so if an order was placed at the same time the just-sold units could be silently restored and oversold. Batch quantity is now only ever changed through a row-locked, relative adjustment, so concurrent sales are always preserved.
  • Fixed (Pro): raising a batch’s quantity above its original amount now also raises the recorded starting quantity, so a later refund or batch swap restores the correct amount instead of being capped too low.
  • Fixed (Pro): plugin deactivation and uninstall now clear the one-off batch-processing event too, and deleting the Free and Pro plugins together reliably honors your “delete all data on uninstall” choice regardless of order.
  • Fixed: the plugin’s CSV export no longer outputs a row for variable (parent) products. Those rows showed the earliest variation date under a generic column and re-importing them set the inherited default date instead, which was confusing; per-variation rows are still exported and imported as before.
  • Regenerated the translation template.

1.0.12

  • Fixed: importing a variation’s expiration date via CSV (and quick/bulk-editing a variable product) now correctly recomputes the parent product’s hide/out-of-stock and reporting dates. Previously a variable product whose variations were all set to expired via CSV import could remain visible and purchasable until the next edit.
  • Fixed: the Reports “Export” button now respects the active Expiring-soon/Expired filter instead of exporting all dated products.
  • (Pro: the hourly expired-batch job now reconciles stock and visibility for every affected product, not just the first 100 when a large number of batches expire in the same run; plugin deactivation now also clears the batch stock-resync cron event.)
  • Regenerated the translation template.

May 29, 2026

Releases: v1.0.7 – v1.0.11

1.0.11

  • No user-facing changes in the Free plugin. (Pro: corrected the License screen description, which listed a “batch CSV import / export” capability that is not part of Pro; Pro CSV support is the WooCommerce product-CSV integration for expiration dates.)

1.0.10

  • Fixed: the one-time upgrade routine that back-fills the “fully expired” date for existing products ran too early in the request (before WooCommerce finished loading), which could mis-handle variable products and emit a “called incorrectly” notice. It now runs once WooCommerce is fully loaded, so variable products are back-filled correctly on upgrade.

1.0.9

  • No user-facing changes in the Free plugin. (Pro: fixed a fatal error (“Unknown named parameter $forced”) that could occur on PHP 8 when the batch-expiry processing cron ran via its one-off “forced” event, which prevented expired batches from being processed on those runs.)

1.0.8

  • Fixed: remaining timezone off-by-one in the reports, dashboard, admin column and CSV date ranges on sites in non-UTC timezones (display only).
  • Internal code-quality annotations; no functional change to the Free plugin.
  • (Pro: fixed a batch swap that could permanently lose stock when the original batch had been deleted or could not receive the returned units — the swap is now refused with a clear message. FEFO: an order that can’t be fully filled from batch stock is now placed on hold for review instead of completing without reducing stock, preventing oversell. Added an uninstall routine that releases the Lemon Squeezy license activation slot and cleans up Pro options; batch inventory data is preserved unless you opt in to delete-on-uninstall.)

1.0.7

  • Fixed: variable products were hidden / set out of stock as soon as their earliest variation expired. A variable product is now hidden or set out of stock only once every variation has expired; partially-expired products keep their still-valid variations available.
  • Fixed: a dateless variation now correctly inherits the parent’s chosen default date (or no date at all) instead of an unintended internal value.
  • Fixed: the hourly out-of-stock sweep could skip products on catalogs with more than 100 expiring items.
  • Fixed: an off-by-one in the expiration threshold near midnight on sites in non-UTC timezones.
  • Hardened: CSV export formula-injection escaping now also catches leading-whitespace formulas.
  • Updated “Tested up to” to WordPress 7.0 and WooCommerce 10.8.
  • (Pro: automatic-discount price/badge cache is now invalidated when discount tiers or the enabled flag change; minor input-sanitization hardening on batch-tracking toggles.)

May 28, 2026

Release: v1.0.6

1.0.6

  • No user-facing changes in the Free plugin. (Pro: hardened the Pro / Free version-compatibility check at activation, runtime, and auto-update time so a Pro update can never apply on top of an incompatible Free version.)

May 26, 2026

Releases: v1.0.3 – v1.0.5

1.0.5

  • Settings: subsections that have no saveable fields no longer render a “Save changes” button at the bottom.

1.0.4

  • No user-facing changes in the Free plugin. (Pro: fixed orphaned batches not being cleaned up when a product is deleted; improved the Activity Log message for expired batches.)

1.0.3

  • Reworded the short description on the Plugins screen to be clearer about what the plugin actually does.

May 15, 2026

Releases: v1.0.1 – v1.0.2

1.0.2

  • Fixed Import / Export page styling — the CSS file was out of sync with the redesigned card-based template, so the page was rendering without card backgrounds or icon styling.

1.0.1

  • Extracted all inline scripts and styles into properly enqueued asset files (wp_enqueue_script / wp_enqueue_style).
  • Added the Requires Plugins: woocommerce header for WordPress 6.5+ dependency handling.
  • Plugin is now fully self-contained: no external service calls, no license gating, all features work out of the box.
  • Updated readme metadata.