License removal and replacement update — 2026-09-23
Form Collector 1.4.5 / License Server 1.23.16

Cause
The old Deactivate button did not remove the saved key. General settings saves copied old license metadata even when a different key was entered. Activation did not read the unsaved replacement input. HTTP failures left previous license_status values intact, while HTTP 200 responses for invalid/expired keys were treated as successful requests. The registered settings sanitizer could also discard status updates written internally by the licensing code.

Fix
License input is now in a dedicated Save & Activate New Key form, protected by manage_options and a nonce. Internal license writes have a narrowly scoped sanitizer bypass for already prepared settings; general settings posts cannot modify or resurrect the key. Changing server connection settings clears validation metadata.
Remove License Key clears the local key and all license-derived metadata first, then calls the supported old-key/site deactivation endpoint. Failure to release the remote activation is explicitly reported as a warning; local removal remains effective. Other sites and the license itself are not revoked.
Save & Activate New Key saves a trimmed new key, clears previous metadata, attempts old-site deactivation, and activates the replacement. An unsuccessful old-site release is reported even if the replacement activates. Blank replacement input is rejected; use Remove to clear a key.
Refresh License Status sends a fresh no-cache /check request for the saved key; no key is generated. Failed, invalid, expired, malformed, or signature-invalid responses cannot retain an old active status. Background checks use the same behavior. Revision tokens reject late responses after removal or replacement, and option caches are invalidated before comparing state.
The license server's check endpoint now respects this site's activation record. Activation/deactivation database failures return errors instead of success.

Preservation
The fixed five-submission lifetime allowance, login gate, daily-key-email fix, stored W-9 records, users, plan limits, server keys and unrelated settings remain. No data deletion or schema replacement is performed. The only new collector field is an internal request revision token.

Tests
Both plugin files pass PHP 8.3 syntax checks. 76 isolated regression checks passed using production methods with simulated WordPress APIs and database responses. These include the previous 46 checks plus removal/re-render, stale general-settings saves, outage warnings, replacement request key ordering, fresh replacement status/expiration/plan, invalid/expired/limit-reached keys, refresh responses and HTTP/malformed/signature failures, late response rejection, background failures, capability and nonce rejection for all three actions, and server deactivation preserving other sites. The registered settings sanitizer was simulated during license writes to exercise the root cause. These are not full live WordPress/browser/database tests.

Installation
Upload each ZIP as a replacement for its existing plugin. Install License Server 1.23.16 on the licensing website and Form Collector 1.4.5 on each collecting website. Do not uninstall or reset plugin data. Back up first. Preserve any separately installed vendor/ PDF libraries when replacing the collector; the original supplied plugins did not bundle those libraries. Existing server URL and shared-secret configuration remain required.

Live verification still required
1. Remove an existing key; reload the settings page and confirm the input stays empty and premium access is inactive. Confirm only this site's activation was released on the license server.
2. Save and activate a different valid key. Reload and confirm the replacement key, plan, status and expiration. Test an invalid/expired key on staging.
3. Refresh a saved key after changing its status/expiry on the server. Confirm updated results. Test a server outage and restoration on staging.
4. Confirm real object-cache/settings behavior, signature configuration, other licensed sites, subscription sync and form submissions. The plugin temporarily clears paid status while checking, and leaves it inactive if verification fails; after connectivity returns use Refresh or Save & Activate as appropriate.
5. Any site previously using a key without a recorded site activation must use Save & Activate New Key to register that same key. Refresh alone does not allocate an activation.

The live site has not been accessed or modified. Earlier installation notes below remain applicable except where superseded by this update.

W9Collector update — 2026-09-23

Versions
- W-9 Form Collector 1.4.4 (replaces 1.4.3)
- W9FC License Server 1.23.15 (replaces 1.23.14)

Changes
- Removes all existing no-argument w9fc_ls_daily schedules on init, including after an in-place upgrade. The old hook has no registered callback. The replacement w9fc_ls_maintenance_daily continues PMS synchronization and renewal/grace reminders; expiration and collector validation schedules remain intact.
- Existing active licenses no longer receive a key every time PMS synchronization or payment/subscription events run. New licenses receive initial delivery, tracked with an option per license. Failed initial delivery can retry. Existing Stripe delivery code and the customer license portal remain intact. Ordinary renewal/grace notices remain enabled according to existing settings.
- Guests see only a login message and link, never the form or its nonce. Anonymous submission requests receive HTTP 403 before processing. Authenticated requests retain nonce validation, rate limits, encryption, and plan-limit checks.
- Uses the configured PMS login page when available (pms_get_page('login', true)); otherwise uses WordPress wp_login_url, including theme/membership filters. The redirect_to parameter points back to the form permalink. A w9fc_form_login_url filter allows integration with a custom login provider. Cache prevention headers and DONOTCACHEPAGE are set for the form.
- The intended allowance is FIVE lifetime submissions per WordPress installation, not per user. No adjustable free allowance or premium override checkbox is exposed. Old custom free-limit values are ignored; the legacy premium_active flag cannot override license_status. Active, valid, and grace license statuses retain the existing paid-plan rules.
- A persistent usage counter is seeded once from all existing stored W-9 entries. New successful submissions increment it; deleting entries no longer restores free slots. Records deleted before this upgrade cannot be reconstructed and are not included in the initial count. Paid monthly counting is unchanged. Concurrent free submissions are serialized using a MySQL/MariaDB named lock; if a lock cannot be acquired the user receives a retry message (HTTP 503).
- Loads WordPress's list-table base class before declaring the plugins' admin table classes, preventing a dependency-order fatal error on frontend/plugin loading.

Data preservation
No user, W-9 entry, license key, or unrelated settings are removed. Existing plugin directory names and option/table names are retained. The release adds a usage counter and initial-delivery markers. Existing PMS mappings, paid limits, renewal notices, and form-email settings remain in place.

Validation performed
PHP 8.3 syntax checks passed for both full plugin files. A 46-check isolated PHP regression harness executed the production plugin methods with simulated WordPress APIs/database operations. It covered guest rendering and direct POST rejection; standard/theme/PMS login links and return targets; authenticated successful submissions and encrypted TIN handling; nonce rejection; free limit, legacy override rejection, usage migration and deletion persistence; paid active/valid/grace, monthly cap, unlimited and expired states; missing-lock rejection; removal of old cron events, idempotent replacement scheduling, preserved expiry schedule; repeated PMS sync with no repeat key email, unchanged license key and synchronized expiration/plan, new-key delivery, pending-to-active delivery, and retry of failed initial delivery.
These are isolated regression checks, not a full WordPress/browser/MySQL integration test. Login URL construction was tested; actual authentication redirects were not executed. Actual parallel MySQL requests were not tested. Mail/PDF delivery was not executed.
PMS login redirect_to handling was also checked against its official WordPress SVN shortcode source; PMS page lookup follows its documented API.

Install/update
1. Back up the site/database and test on staging first.
2. Upload each ZIP to the site where that plugin is already installed via Plugins > Add New > Upload Plugin, then choose to replace the existing version. Keep existing plugin directories. Do not uninstall the old plugin first.
3. If the collector installation has optional vendor/ PDF libraries installed separately, back those up and restore them after replacement; these original ZIPs did not include those libraries, and the update ZIPs do not add them. Keep the configured IRS PDF template.
4. Purge WordPress/hosting/CDN page caches and exclude form pages from full-page caching. Already cached pages can be served before plugin PHP executes.
5. Visit the site once after updating; upgrade migration runs on init without requiring deactivation/reactivation.

Required staging/live checks
- Confirm versions and existing entries, settings, users, and keys remain visible.
- In a private browser, confirm the form page shows only the login message. POST action=w9_submit to wp-admin/admin-post.php while logged out: expect 403 and no new entry.
- Follow the login link through the site's actual theme/PMS/SSO flow, confirm return to the correct form page, and submit as an eligible user. Repeat with a session that expires before submission. Check any site-specific login redirect rules.
- On a staging copy, confirm the fifth free submission works, the sixth is denied, deletion does not restore allowance, and legacy custom settings cannot bypass the cap. Confirm the host supports GET_LOCK and concurrent free requests cannot exceed the allowance.
- Confirm licensed plans accept allowed submissions, enforce monthly limits, and retain unlimited-plan access. Exercise a purchase/renewal/cancellation/expiration and license activation/check/deactivation against the real server.
- Inspect WP-Cron after the first request: w9fc_ls_daily should be absent; w9fc_ls_maintenance_daily should appear once, alongside the retained expiration and collector validation jobs. Run maintenance twice and verify no activation-key email is sent to an existing active customer. Test one new purchase for initial delivery. Observe the next scheduled run.
- Test actual email delivery, attachments, optional PDF rendering, reCAPTCHA, and site caches.

No live site was accessed or modified for this update.
