Easy Invoice — complete changelog The readme summarises each release; this file keeps every entry. = 2.4.4 - September 29, 2026 = * Fix: **accepting a quote sent the client to a signed "secure link" even on sites that never switched Secure Links on.** The acceptance response asked for a signed URL whenever Easy Invoice Pro was installed — it tested that the class could be loaded, not that the feature was enabled — and minted a hash for the new invoice on the way. Every other place that can use a signed URL (emails, the shortcode, reminders) already checked the setting; this one now does too. * Change: the Secure Links add-on card explains that switching it on adds the setting, and that signing starts when you enable it under Settings → Advanced. = 2.4.3 - September 29, 2026 = * Fix: **every A4 document spilled a sliver onto a second page.** The "Download as PDF" button captures the page as shown, and the sheet the designs lay out is exactly A4 — but the capture was given only 288mm of each 297mm page, because a 9mm strip was reserved for the page label. The last 9mm of the sheet, blank or not, became page two. One sheet is now one page; the label for a genuinely multi-page file is drawn inside the sheet's own bottom margin. * Fix: the Creative design drew a sheet about 200px taller than the A4 page it sits on, so even a one-line invoice captured as two pages. Its spacing scale is compressed on the card; the design keeps its proportions. * Fix: where a page's capture stopped short of the foot of the sheet — the last page, or one that broke early at a row — the rest of the page was left unpainted, which some readers show as a black band. Every page is painted white first. * Fix: **PDFs broke onto a second page while a third of the first page sat empty.** Six of the nine designs lay themselves out on a rem spacing scale sized for a screen, and that scale reached the PDF untouched: the print stylesheet sits in the document head, while each design declares its own values further down, so the design always won. The print sheet's values now take precedence, and the spacing scale, display type, line spacing, meta rows and the wrappers between sections are all compressed for paper. A six-line invoice that took two pages in eight of the nine designs now fits on one in all nine; long documents still break where they should and the items table repeats its header. = 2.4.2 - September 29, 2026 = * Fix: the invoice and quote builders could fatal instead of rendering when the preview ran before a document existed; the preview now shows a placeholder until there is something to draw. * Fix: **a PayPal payment smaller than the invoice total no longer marks the invoice paid.** When PayPal confirmed less than was requested, the pending record was flipped to "completed" before its amount was corrected, so anything reading the payment at that moment still saw the full balance. The settled amount is written first; the invoice lands on Partially paid with the real balance owed. * Fix: the plugin's own sidebar logo rendered as an empty coloured square — `wp_kses_post()` strips ``. The mark (and any logo the White-Label addon substitutes) now goes through an SVG-aware allow-list. * Change: the WordPress admin menu uses the plugin's own icon instead of a generic dashicon, drawn so WordPress recolours it with the admin colour scheme. * Fix: **offline payments work in the free plugin.** Choosing "Manual Payment" on an invoice showed no instructions, no receipt upload and recorded nothing — the pieces existed but were never wired together. Bank transfer, cheque and cash are now three proper payment methods: each shows your details and instructions, can ask for the client's transaction reference and a receipt (optional or required), and records a pending payment for you to confirm. * New: **add your own offline payment methods** (ACH, wire transfer, mobile money, a second bank account…) under Settings → Payment Methods → "Add an offline payment method". Each gets its own instructions, reference field and receipt setting, appears in the Pay Now panel, and can be removed again; payments already recorded keep it. * New: **confirm or reject** each submitted offline payment from the Payments screen. The client's reference, note and receipt are shown on the payment row and the payment page; the admin email carries them too. Confirming marks that payment completed and moves the invoice to Paid or Partially paid by its real balance (it used to mark the invoice paid whatever the amount); rejecting keeps the record for the audit trail and leaves the balance owed. The client sees "payment noted — awaiting confirmation" on the invoice page meanwhile. * Security: receipts are stored under random names in a folder that refuses direct requests on Apache, and are only ever served to signed-in staff through the plugin (`admin-post.php?action=easy_invoice_payment_proof`). Receipts are deleted with their payment and on a full uninstall. * Fix: the Payments screen's "Total amount" counted submissions still awaiting confirmation and rejected payments as money received; it now sums confirmed payments only. The status filter gained "Awaiting confirmation" and "Rejected". * Fix: the free payment reminder never ran (it looked for a status nothing writes). Settings → Advanced → "Payment reminder (days after due date)" now emails the Reminder template once to each invoice still owed that many days after its due date; leave it empty to switch it off. Easy Invoice Pro's own reminders take over when Pro is active. * Fix: an invoice whose payment methods were limited to "Manual Payment" before this release now offers the three offline methods instead of nothing. * Change: a brand-new install starts with Bank transfer enabled, so an invoice can be paid from day one; a client can have at most three offline submissions awaiting confirmation on one invoice (filter `easy_invoice_offline_pending_limit`). = 2.4.1 - September 15, 2026 = * Fix: on phones the public invoice and quote pages clipped the line-item table's Total column and, in the Modern, Creative and Professional designs, the totals box; every design now fits a 360px screen (narrower screens scroll the table sideways). * Performance: the payment script (and the jQuery it needs) loaded on every front-end page of the site; it now loads on the public invoice page only. Filter `easy_invoice_load_payment_assets` to add pages. * Housekeeping: the changelog in this file now summarises each release; the complete, itemised history lives in changelog.txt inside the plugin folder and on GitHub. = 2.4.0 - September 11, 2026 = * Fix: on phones the public invoice and quote pages clipped the line-item table's Total column and, in the Modern, Creative and Professional designs, the totals box; the sheet now uses phone margins, item names wrap and the figures stay on one line, so every design fits a 360px screen (narrower screens scroll the table sideways). * Performance: the payment script (and the jQuery it needs) loaded on every page of the site; it now loads on the public invoice page only. Filter `easy_invoice_load_payment_assets` to add pages. * Change: a link emailed before access keys existed no longer dead-ends at "not found": it shows a page (no document data) offering to email a fresh keyed link to the address the document was issued to — one click for the customer, nothing for the merchant to do. Rate-limited; drafts are never offered. * Fix: the quote list's header cards (total quotes, value by currency, accepted, pending) counted only the 20 rows on screen; they now cover the whole store, from stored quote totals. * Fix: searching the quote list by quote number, client name or email returned nothing; only title text matched. * Performance: the invoice list's Overdue filter, the Add Payment form's invoice picker, the payments list totals and the site-wide outstanding/revenue figures are answered from single SQL passes (10,000-invoice stores: Overdue filter 19 s → 3 s, Add Payment 2 s → 0.4 s). * Fix: two builders open at the same time could save two invoices (or quotes) with the same number — the number the page showed on load overwrote the one claimed on save. Quotes were affected on every concurrent save. * Fix: quote builder client search ignored a name typed while the initial "all clients" list was still loading. * Fixed - **"Apply Tax to This Invoice" could never be switched off.** The builder omits an unchecked box from its request and the form processor treated "absent" as "unchanged", so with tax enabled globally every new invoice came back taxed and unticking the box on an existing one was silently ignored. Absent now means unchecked, as the browser intends. * Fixed - **A discount could make an invoice or quote total negative.** A percentage above 100 or a fixed amount larger than the subtotal was accepted and produced a negative total (and a negative balance due). Both builders now refuse a percentage over 100 or a negative discount with a clear message, the message itself is shown in the toast instead of "Validation failed", and the models cap any discount at the amount it is taken from. * Performance - **Invoice totals are now stored.** Until now a total only existed while a model was in memory, so the invoice list header, the dashboard, the clients page and the reports loaded every open and every paid invoice as a full model on each view — 20 s and 88 MB with 1,200 invoices, and out of memory at a few thousand. `InvoiceTotalsCache` persists the computed total on save, backfills older invoices in the background (a one-off cron job; nothing to run by hand), drops a total when its inputs change (items, discount, tax fields, the global tax settings) and rebuilds everything when the arithmetic itself changes. Those screens now sum in SQL, with the site-wide figures memoised until the next invoice, payment or credit note write. Verified on 2,300 invoices added through the builder: list 20 s → under a second of plugin time, 88 MB → 12 MB. * Fixed - The invoice list's "Total Invoices" card and "All (n)" tab showed the size of the current page (20) instead of the real count, and counted trashed and draft invoices by loading every one of them. * Fixed - **Overdue and partially paid invoices could not be paid from their public page.** The payment panel only offered methods while the status was "available" or "unpaid", so an overdue invoice — the one a client most needs to settle — showed "Payment methods are not configured". Every status that still carries a balance now offers the configured methods (`easy_invoice_payable_statuses` filter). The panel also no longer throws a script error when no method is available. * Fixed - After saving a brand-new invoice or quote the address bar never gained the document id, so a refresh reopened a blank builder and a second Save created a duplicate. The id is now written into the URL as soon as the save succeeds. * Performance - Payments → Add New Payment built a full model for every published invoice on the site to fill its drop-down; on a store with thousands of invoices that was tens of seconds and hundreds of megabytes. It now lists open invoices only (newest 300, filterable, `easy_invoice_payment_form_invoice_limit`), shows the balance still due, and preselects `?invoice_id=`. * Performance - The invoice and quote builders loaded every client on the site into a hidden mirror `` + `window.location.replace`) when the server-side redirect can't fire because upstream output already flushed the response headers. Email download links, dashboard widgets, and any other integration calling these endpoints directly benefit from the same hardening. = 2.3.6 - June 26, 2026 = * Security - Tightened the capability check on the AJAX payment-update endpoint so only users with the dedicated payment-management permission can change payment records or invoice status. **All sites should update.** * Security - The `[easy_invoice_url]` and `[easy_quote_url]` shortcodes no longer generate per-document access keys for arbitrary visitors. Keys are now produced only when an invoice or quote email is sent; the shortcodes attach an existing key only when the current viewer is the site admin, the bound client, or already holds the key in the page URL. * Fixed - The bound-client authorisation path on quote Accept / Decline and invoice manual-payment submission silently never succeeded for logged-in customers. The guard relied on PHP's `method_exists()` which returns false for `__call`-resolved methods, and both the Invoice and Quote models resolve `getClientId()` that way. Logged-in clients whose email matches the document's bound client are now correctly recognised. (Admin and emailed-link paths were unaffected.) * Note - Emailed `{{invoice_url}}` and `{{quote_url}}` links continue to work unchanged for the legitimate recipient. The shortcode change is invisible for admin embeds on admin-context pages; on public pages the shortcode now renders a plain permalink for visitors who don't already hold a valid key (they can still view the document — only the Accept / Decline / submit-manual-payment paths require the key). = 2.3.5 - June 26, 2026 = * Security - Hardened authorisation on the manual-payment submission flow. **All sites should update.** * Improved - Invoice share links emailed to clients (`{{invoice_url}}` in email templates, `[easy_invoice_url]` shortcode) now carry a per-invoice access key. Recipients of these links can submit "I paid by bank transfer / cheque" entries as before — no extra steps for the customer. * Note - Invoice links generated **before** this update will still load and display the invoice and accept online gateway payments (Stripe / PayPal / etc.). The manual-payment submission form appears only after the admin resends the invoice (which produces a new link) or after the client logs in to the WordPress account whose email matches the invoice's bound client. Admins are unaffected — the form is always available from the admin UI. * Fixed - Race condition under high concurrency where two simultaneous invoice (or quote) creates could be assigned the same number. The counter read-check-write is now serialised via a MySQL named lock; the lock auto-releases on connection close so it cannot leak across requests. * Added - License recognition for the new Professional Lifetime and Agency Lifetime SKUs so customers on those plans are correctly placed in their tier and see the matching variant label on the License page. = 2.3.4 - June 12, 2026 = * Security - Hardened authorisation on the quote Accept and Decline flows. **All sites running 2.3.3 or earlier should update.** * Improved - Quote share links emailed to clients (`{{quote_url}}` in email templates, `[easy_quote_url]` shortcode) now carry a per-quote access key. Recipients of these links can Accept / Decline as before — no extra steps for the customer. * Note - Quote links generated **before** this update will still load and display the quote, but the Accept / Decline buttons will appear only after the admin resends the quote (which produces a new link) or after the client logs in to the WordPress account whose email matches the quote's bound client. Admins are unaffected — Accept / Decline from the admin UI continues to work for every quote. = 2.3.3 - June 06, 2026 = * Fixed - **Client role badge inconsistent across sites: "Client" on some, "Customer" on others.** Root cause was a four-year-old hook-timing bug in `ClientServiceProvider::register()` — it called `add_action('after_setup_theme', ...)` from inside a callback that itself only runs on `init:10`, so by the time the `add_action` ran, `after_setup_theme` had already fired and the registration silently never executed. On sites with WooCommerce, WC registered `customer` so the badge showed "Customer"; on sites without WC, the role was never registered, `WP_User::roles` filtered it out as unknown, and the Clients page fell back to a "Client" label. Hook moved to `init:1` so it actually runs. * Fixed - **New clients created from Easy Invoice no longer have backend access.** Previously, new clients were assigned WooCommerce's `customer` role (or our copy of it) which carries the `read` capability — letting them log in to wp-admin and see a stripped-down dashboard. New clients now receive a per-user `read => false` capability override on creation: they keep the `customer` role (so Pro Client Portal's role check still recognises them) but `WP_User::has_cap('read')` returns false, blocking wp-admin access. Login still works (auth is cap-free) so the Pro Client Portal frontend remains accessible. * Added - **`easy_invoice_can_edit_invoice` filter hook** in the invoice save AJAX handler and in `Invoice::saveItems()`. Defaults to `true` (no change for sites without addons listening). Pro's Partial Payments addon hooks into this to block edits on paid deposit invoices. * Added - **Locked-banner UI in the invoice editor.** When an invoice's `easy_invoice_can_edit_invoice` resolves to `false`, the editor renders an amber notice at the top: *"This deposit invoice is locked. A payment was recorded on [date]. To add new charges, open the linked balance invoice instead."* with a button that deep-links to the paired balance invoice. The hidden `
` gets a `data-ei-locked="1"` attribute so themes/addons can extend the lock visually. * Important - **Existing users' access is preserved.** The `customer` role is now registered with **no** capabilities (`[]`) so that users on sites that previously had the broken registration don't suddenly gain a `read` cap on the upgrade. On sites where WooCommerce has already registered `customer` with its full cap set, our `add_role()` is a no-op (the role already exists) — WC customers retain WC's caps unchanged. The new per-user `read => false` only applies to clients created from 2.3.3 onward. = 2.3.2 - May 24, 2026 = * Fixed - **Client search now reliably finds WooCommerce customers.** Three compounding bugs were producing intermittent / empty search results in the invoice and quote client-search dropdown: - Three handlers were registered for the same `wp_ajax_easy_invoice_search_clients` action (in `EasyInvoiceAjax`, `InvoiceController`, and `QuoteController`). WordPress fires hooks in registration order; the first to call `wp_send_json_success()` died the request, and which one won shifted between requests based on bootstrap order. Removed the two duplicate registrations — `EasyInvoiceAjax::searchClients` is now the sole owner. - `EasyInvoiceAjax::sendSuccess()` was injecting a `toast` key into the response data unconditionally. For the search endpoint that returns a numeric-indexed array of clients, adding a string key mutated the array shape so `json_encode` emitted a JSON object instead of an array. The frontend check `response.data.length > 0` then evaluated `undefined > 0` and silently rendered the empty state. `sendSuccess` now only injects the toast key when `$data` is associative or empty — list responses pass through unchanged. - `searchClients` itself now bypasses `sendSuccess` entirely (via `wp_send_json_success` direct) so the "Operation completed successfully" toast no longer appears on every keystroke. * Fixed - **WooCommerce customers display with their real name everywhere.** The `Client` model loaded only Easy-Invoice-specific meta keys, so WooCommerce-imported customers rendered as their bare `user_login` in the Clients listing, the invoice/quote search dropdown, and the invoice header — even though their real name sits in `billing_first_name` / `billing_last_name`. Extended `Client::loadFromUserMeta()` with a unified fallback chain: EI meta -> WP standard meta -> WooCommerce `billing_*` meta. Covers `first_name`, `last_name`, `email`, `phone`, and `business_client_name` (from `billing_company`). Single change propagates to every display point through the model's magic getters. * Improved - **Client search now covers phone numbers across both client pools.** `ClientRepository::search()` already searched `billing_phone` so WooCommerce customers were searchable by phone, but it was missing the Easy-Invoice-native phone meta key. Added `ClientFields::PHONE` so EI-native clients are searchable by phone on equal footing with first name, last name, email, and business name. * Fixed - **Item Library buttons hidden on invoice/quote forms after addon migration.** The invoice and quote item rows render "Select from Item Library" and "Save to Item Library" icon buttons gated on the Item Library service being available. The gate was a stale `class_exists('EasyInvoicePro\\Services\\ItemLibraryService')` check left over from before the addon-system migration — the class moved to `EasyInvoicePro\Addons\ItemLibrary\Services\ItemLibraryService` so the old check always returned false, hiding the buttons even when the addon was active. Replaced with the canonical `\EasyInvoice\Addons\AddonManager::shouldLoad('item_library')` check used everywhere else in the codebase. * Fixed - **Clients table row alignment after Add New Client.** The table has 8 columns (ID, Business Name, Client Name, Email, Phone, Username, Role, Actions) but the JS row template only rendered 6 cells, so every column from "Client Name" onward landed one position to the left of where it should — email landed in the Client Name column, role badge landed in the Phone column, action buttons landed in the Username column. Restored the missing Business Name and Phone cells and aligned the `colspan` for the "No clients found" placeholder across PHP (was 7) and JS (was 6) to the correct 8. * Fixed - **No success toast after Add Client.** The success branch was calling a non-existent `showToast()` function behind a `typeof showToast === 'function'` guard, so the toast never fired. Switched to `EasyInvoiceToast.success` / `.error` (the same API used by every other handler in the file). Kept the `suppress_global_toast` flag so we don't get a duplicate toast now that the manual one works. * Fixed - **Role badge flickered "Client" -> "Customer" after refresh.** New clients are created with the WP `customer` role, but the JS new-row template hardcoded "Client" — producing a one-shot mismatch the user saw on the next page load. The `addClient` AJAX response now returns the actual assigned role (`role` + `role_label`) and the JS template renders from that. Single source of truth in PHP, no more flicker. * Improved - **Short description shortened to fit the WordPress.org 150-char limit** (was 157 chars and got truncated by the importer with a warning visible to plugin authors). New version keeps the "WordPress invoice plugin" keyword up front and packs the four primary capabilities into a clean two-sentence snippet. = 2.3.1 - May 21, 2026 = * Compatibility - **Tested and confirmed compatible with WordPress 7.0.** "Tested up to" bumped from 6.9. * Fixed - **Client search excluded WooCommerce customers.** `ClientRepository::search()` gated all results on an EXISTS check against Easy Invoice's own meta keys, so users who exist in WordPress (or were imported by WooCommerce) but had no prior Easy Invoice activity were filtered out of every client autocomplete and selector. Replaced with a two-pass query: a user-table search across `user_login`, `user_email`, `display_name`, plus a meta search across `first_name`, `last_name`, `nickname`, `billing_first_name`, `billing_last_name`, `billing_email`, `billing_company` and Easy Invoice's own meta keys. Result limit raised from 10 to 50. * Fixed - **License Plan Resolver: Personal license showed as Professional.** `PRICE_ID_MAP` mis-mapped EDD `price_id` 1 to Professional. Corrected mapping to: 1=Personal, 2=Professional, 3=Agency, 4=Personal Lifetime, 5=Personal Lifetime (Unlimited). Also fixed the name-based fallback that was matching "Easy Invoice **Pro**" against "Professional" via `strpos` — order is now agency → professional → personal so the parent name no longer poisons the match. * Added - **Plan variant labels.** Personal Lifetime and Personal Lifetime (Unlimited) now surface as "Personal Lifetime" on the License page chip instead of generic "Personal". * Fixed - **License page activation count** showed "1 / 0" for unlimited-activation keys (EDD returns `license_limit: 0` for unlimited). Now renders "1 / Unlimited" when limit is `0` / empty / the string "unlimited". * Improved - **License page UI**: variant badge inline with the Plan chip, "12 months" expiry shown inline, container widened and centered on the page, removed full-screen gray background that bled across all admin pages (`.easy-invoice-admin` now uses `bg-white`). * Added - **"Docs" link on every Addons-page card state.** Previously only Locked / Available cards linked to docs; the Active state now also exposes the Docs link so users can find documentation for addons they're already using. * Improved - **Privacy & GDPR settings section** renamed from "Account Settings" — clearer naming that matches the addon that owns the surface. Section is hidden entirely when the Privacy & GDPR Tools addon is not active. * Internal - One-time cleanup script removed 4,315 duplicate Client Portal pages created by a regression in the page-existence check. New code defers portal-page setup to a manual admin notice rather than auto-creating on every request. = 2.3.0 - May 17, 2026 = * Added - **Addon system for Easy Invoice Pro**. Every Pro feature is now an independently-toggleable addon under **Easy Invoice → Addons**. Disabled addons contribute zero PHP, zero database queries, zero hooks. * Added - **Personal tier is now free with the Pro plugin** (no license key required). Eleven addons unlock automatically when you install Easy Invoice Pro: Recurring Invoices, Partial Payments & Deposits, Client Portal, PDF Toolkit, Bulk Email & Export, Item Library, Custom Invoice & Quote Templates, Additional Tax Lines, Email Enhancements, Privacy & GDPR Tools, and Reports & Analytics. A license key now only unlocks Professional and Agency tiers. * Added - **Reports addon** (Personal tier) — gating the Reports page on the new addon flag so users can disable it if not needed. * Added - **Per-addon sidebar navigation** below the Addons menu in the Easy Invoice in-app sidebar — every enabled addon with a primary page gets its own sidebar entry automatically. * Added - **WP admin notice suppression** now extended to every addon page (dynamic — picks up new addons automatically from the registry). * Added - **One-time migration** (`MIGRATION_VERSION = '2'`) auto-enables every Personal-tier addon for existing Pro installs based on detected feature usage, so nothing breaks on upgrade. New installs get addons disabled by default — opt-in only. * Improved - **Free vs Pro page** rewritten as a data-driven layout (680 lines → 200 lines) with the new four-tier plan model (Free / Personal / Professional / Agency) and concrete addon names per row. * Improved - **Addons page UI**: every card shows the addon's full description (line-clamped to 3 lines), tier-color rail removed in favour of plan pills, addon categories regrouped into 8 meaningful buckets (billing, branding, productivity, clients, team, integrations, compliance, analytics). * Improved - **Addon card descriptions** rewritten for every addon — benefit-led, concrete, complete sentences with no ellipsis truncation. * Improved - **License banner** on the Addons page now correctly reflects the new Personal-tier-is-free model: free users see "Get Easy Invoice Pro", Pro-installed-without-license users see "Activate License to unlock Professional / Agency". * Improved - **Item Library** + **Template Builder** pages now appear only in the in-app sidebar (under the Addons menu) — the duplicate WordPress admin submenu entries were hidden so users don't see the same link twice. * Improved - **Export All** buttons on invoice, quote, payment, and client list screens are now gated on the `bulk_operations` addon. Free users still see the upsell button; Pro-without-addon hides it cleanly. * Improved - **Templates moved** into per-addon folders (`addons/{slug}/templates/`) so each addon owns its UI alongside its logic. * Internal - Five settings controllers had bottom-of-file `register()` calls that fired on autoload regardless of addon state. Removed; registration now happens only inside the addon wrapper. * Internal - **AddonRegistry** is now the single source of truth for all 16 addons; the Free vs Pro page, addons grid, sidebar, and notice-suppression list all derive from it. = 2.2.1 - May 15, 2026 = * Fixed - **PDF download quality**. Invoice / quote PDFs now render with crisp text and sharp table borders instead of soft, slightly-blurry output. Specifically: - html2canvas capture scale bumped from 1.5x to 2x (matches retina pixel density). - Switched the embedded bitmap from JPEG @ 0.92 to lossless PNG so text edges and table grid lines no longer suffer JPEG colour-fringe artefacts. - Locked the capture viewport to the document's own layout width so responsive CSS can't shrink columns mid-capture. - Enabled jsPDF FlateDecode compression and the slow / higher-quality addImage compression flag — file sizes stay reasonable despite the higher resolution. * Internal - JS cache buster bumped so browsers pick up the new `document-pdf.js` on first reload after upgrade. = 2.2.0 - May 13, 2026 = * Added - **Export Selected (Pro)** option in the Bulk Actions dropdown on both Invoice and Quote listings — visible to all users so the feature is discoverable. Pairs with the existing **Send Email (Pro)** option. * Added - When Easy Invoice Pro is inactive, picking either Pro-gated bulk action now opens the project's existing Upgrade-to-Pro dialog with feature-specific copy (handled by the new `bulk-send-email-teaser.js`). When Pro is active, Pro's own handlers take over and actually run the action behind a Confirm dialog. * Improved - All Pro-gated submit interceptors moved to native **capture-phase** event listeners so they win against the listing template's own inline jQuery submit handlers (which previously raced our handlers and showed a generic "Confirm Action" prompt on the Quote listing). * Improved - Settings → Payment now shows a clean "Unlock more payment gateways" teaser block when Pro is inactive, listing every Pro gateway with one-click upgrade links. * Fixed - Quote listing search form posted to a non-existent page slug (`easy-invoice-quotes-all`); now correctly posts to `easy-quote-all`, so picking a value from the client filter no longer redirects to "you are not allowed". * Fixed - Default Invoice / Quote email body shipped a leftover `Shortcode: [easy_(invoice|quote)_url …]` reference line that got emailed to clients as raw text. The line is removed from the defaults, and a one-time migration strips it from already-saved options. = 2.1.22 - May 13, 2026 = * Added - **Filter by Client** dropdown on both **All Invoices** and **All Quotes** listing pages. Persists through view-tab switches, search, pagination, and status-filter chips so you can drill into a single client's history in two clicks. * Improved - Listing controllers now emit the available clients list and the selected `client_id` to the template, exposing a clean extension point for Pro modules. = 2.1.21 - May 11, 2026 = * Added - "Documentation" link in the Easy Invoice plugin sidebar (under Join Community) that opens the new official documentation site at https://easy-invoice.matrixaddons.com/docs/ * Updated - All in-plugin documentation references now point to the new dedicated docs site (https://easy-invoice.matrixaddons.com/docs/) instead of the legacy URL * Updated - readme.txt documentation links refreshed across Description, Installation, FAQs and Support sections = 2.1.20 - April 29, 2026 = * Security: Manual payment proof uploads now validate file contents with WordPress APIs (`wp_check_filetype_and_ext`) and store only a safe extension; client-supplied multipart MIME types and filenames are not trusted. * Security: Quote accept and decline (AJAX and POST forms) use a per-quote nonce and load published quotes for non-administrators, preventing cross-quote reuse of a nonce and blocking actions on non-published quotes for guests. * Security: Unauthenticated payment-instructions AJAX only applies to published invoices. * Security: Send invoice and send quote email AJAX use a single handler with published-document checks for guests; removed duplicate AJAX registration that could double-send or bypass publication checks. = 2.1.19 - April 27, 2026 = * Minor UI issue fixed * Minor bug fixed on both plugins = 2.1.18 - April 21, 2026 = * Maintenance - Version bump release = 2.1.17 - April 17, 2026 = * Fixed - Client PDF download: long invoice/quote HTML no longer produces blank or truncated PDFs (JPEG/PNG mismatch fixed; browser canvas limits handled; tall content captured in slices and split across A4 pages) * Fixed - PDF page breaks align to table row and summary block boundaries so line items are not cut in half across pages (row-aware cropping) = 2.1.16 - April 16, 2026 = * Fixed - Quote builder item total now correctly applies Adjust (%) in real-time calculation * Fixed - Tax now only applies to items with the Taxable checkbox checked (both Invoice and Quote builders) * Fixed - Discount Calculation Method (Before Tax / After Tax) now works correctly in both builders * Fixed - Price Includes Tax setting is now respected in all tax calculations * Fixed - Calculation Method field correctly reads discount_calculation_method instead of incorrect radio selector * Fixed - Invoice builder payment manager fallback now correctly finds quote items (.quote-item class) * Fixed - Invoice discount type None now correctly returns zero discount * Fixed - Division by zero guard added in tax discount ratio calculation * Fixed - InvoiceRepository now correctly saves discount_calculation_method from form submission * Improved - Quote form event listeners now respond to Taxable checkbox, Calculation Method, and Price Includes Tax changes * Fixed - Invoice adjust field setting now properly controls the adjust field display in Invoice Builder * Fixed - Corrected method calls to use shouldShowInvoiceAdjustField() instead of shouldShowQuoteAdjustField() * Improved - Invoice Builder now correctly responds to Show/Hide Adjust Field setting * Fixed - Adjust field calculations now completely ignored when Show/Hide Adjust Field setting is disabled * Fixed - JavaScript calculations now respect adjust field setting via localized showAdjustField property * Fixed - Invoice and Quote models now check adjust field setting before applying adjustment calculations * Fixed - QuoteItem::getAmount() now respects shouldShowQuoteAdjustField() setting for real-time live preview * Fixed - InvoiceItem::getAmount() now respects shouldShowInvoiceAdjustField() setting for real-time live preview = 2.1.15 - April 16, 2026 = * Fixed - Invoice adjust field setting now properly controls the adjust field display in Invoice Builder * Fixed - Corrected method calls to use shouldShowInvoiceAdjustField() instead of shouldShowQuoteAdjustField() * Improved - Invoice Builder now correctly responds to Show/Hide Adjust Field setting = 2.1.14 - March 31, 2026 = * Improved - Invoice and quote builder experience: Editor/Preview shell tabs, localized send-email confirmation, header title aligned with the document title field, dismissible mobile hint, and unsaved-changes warning when leaving the page * Added - Accessible labels on invoice and quote form section navigation * Improved - Table sorting functionality to handle new column structure * Fixed - Client name display logic for better clarity and organization = 2.1.13 - March 22, 2026 = * Improved - User experience with better visual hierarchy * Fixed - Minor styling issues in pricing display = 2.1.12 - March 13, 2026 = * Fixed - Minor issues fixed * Added - Promotional notice = 2.1.11 - February 22, 2026 = * Fixed - Minor issues fixed = 2.1.9 - February 15, 2026 = * Fixed vendor dependency issue affecting plugin initialization * Improved compatibility with latest WordPress version = 2.1.8 - February 15, 2026 = * Added custom CSS section for each template * Enhanced template flexibility for developers = 2.1.7 - December 15, 2025 = * Fixed quote accept and decline functionality * Improved quote workflow reliability = 2.1.6 - December 11, 2025 = * Tested and confirmed compatibility with WordPress 6.9 * Performance optimizations = 2.1.5 - November 20, 2025 = * Fixed template loading issue * Improved template rendering performance = 2.1.4 - November 11, 2025 = * Fixed email sending issues with SMTP configurations * Improved HTML tag support in textarea fields * Fixed empty data saving issue in description fields = 2.1.3 - November 3, 2025 = * Added review notice * Added community link for support = 2.1.2 - October 30, 2025 = * Fixed phone number formatting and validation * Fixed shortcode rendering issues * Fixed fatal error during email sending = 2.1.0 - October 13, 2025 = * Enhanced data escaping for security * Strengthened input sanitization = 2.0.9 - August 30, 2025 = * Fixed quote and invoice auto-increment number generation * Added regeneration feature for quote and invoice numbers = 2.0.8 - August 30, 2025 = * Updated checkout link for improved payment flow = 2.0.7 - August 17, 2025 = * Fixed minor UI and functionality issues * Fixed trash button behavior = 2.0.6 - August 27, 2025 = * Fixed quote acceptance workflow = 2.0.5 - August 24, 2025 = * Fixed date format display issues * Fixed various minor bugs = 2.0.4 - August 21, 2025 = * Fixed currency symbol display issues * Fixed quote line items calculation = 2.0.3 - August 19, 2025 = * Fixed data migration from version 1.x to 2.0 = 2.0.2 - August 19, 2025 = * Various bug fixes * Added legacy template support = 2.0.1 - August 16, 2025 = * Fixed currency formatting and display * Fixed PDF size and rendering * Fixed email sending functionality * Added {{quote_url}} and {{invoice_url}} smart tags [View complete changelog](https://matrixaddons.com/plugins/easy-invoice/#changelog)