Skip to content

Releases: ChoiceOMG/choice-uft

Version 3.28.4

Choose a tag to compare

@github-actions github-actions released this 24 Sep 16:44

Fixed

  • Click tracking table now installs on hosts whose default storage engine is MyISAM (or InnoDB with the old 767/1000-byte index limit). The unique index on click_id covered all 255 utf8mb4 characters (1,020 bytes), which MySQL rejects there with "Specified key was too long; max key length is 1000 bytes", so the table was never created and no clicks were recorded. The index now covers the first 191 characters; click IDs are far shorter, so uniqueness is unchanged, and existing tables are not altered.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.28.4.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.28.3

Choose a tag to compare

@github-actions github-actions released this 24 Sep 16:35

Fixed

  • Click table repair now records MySQL's own error text. 3.28.2 read the error after running its existence check, which clears it, so failures showed "Unknown database error". When the table is still missing after dbDelta(), the CREATE is run once directly to capture the error, and a failed repair waits an hour before retrying instead of retrying on every admin page load.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.28.3.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.28.2

Choose a tag to compare

@github-actions github-actions released this 24 Sep 05:38

Fixed

  • Click writes that fail leave no trace. CUFT_Click_Tracker::track_click() only logged on success; a failed insert or update returned false silently, and CUFT_Click_Tracker::create_table() trusted dbDelta's own report (which logs "Created table" before it runs the query and does not surface a failed CREATE TABLE). A write failure is now logged through CUFT_Logger at error level and recorded, with no PII (no IP, no user agent, only a short prefix of the click id), in a small cuft_last_click_write_error option, surfaced as a notice on the Click Tracking admin page.
  • Error-level log entries are now recorded even with debug logging off. CUFT_Logger::log() previously discarded every entry, including failures, unless "Enable Debug Logging" was already on, so a site had to be reconfigured before a failure could be seen. Error-level entries now bypass that gate; every other level still requires debug logging.
  • Schema self-heal. A site whose click tracking table went missing, or whose table predates the events (3.12.0) or ga_client_id (3.22.0) columns, is repaired automatically: CUFT_Click_Tracker::self_heal_schema() runs on admin_init and after a version change, verifies with a real SHOW TABLES/SHOW COLUMNS check (not just what a migration recorded), and creates or alters what is missing. The result is cached per plugin version so a healthy site costs no extra query on repeat admin page loads.
  • Click Tracking admin list hid real rows by default. filter_has_events defaulted to "Has Events", which excludes a gclid-only visit that never fired a form event; a site with real click rows could look empty. Defaults to "All" now; the filter is still available. The stats tiles and CSV exports already shared the same query, so they are consistent by construction.

Fixed (internal)

  • CUFT_Logger::log() now tolerates the historical call shape used across this codebase, CUFT_Logger::log( 'error', 'message' ) (level and message reversed from the documented signature), by detecting and normalising it, so those entries are stored under the level the caller meant.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.28.2.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.28.1

Choose a tag to compare

@github-actions github-actions released this 24 Sep 05:09

Fixed

  • generate_lead fired twice for any lead with email, phone and a click ID. cuft-dataLayer-utils.js pushed the broad generate_lead (email present) and then, inside the qualify_lead block, dual-fired a second generate_lead with the strict payload and cuft_deprecated: true / cuft_migrate_to: "qualify_lead". That dual-fire was meant to last one version after the April 2026 event rename (3.27.0) and was never removed. It is gone; generate_lead now pushes and records at most once per submission. qualify_lead is unchanged, and the cuft_generate_lead_enabled gating added in 3.28.0 still applies. A GTM trigger on the event name generate_lead alone needs no change beyond seeing one event per lead instead of two; a trigger conditioned on cuft_deprecated equals true will stop firing and should be migrated to qualify_lead, which was always the intended replacement.
  • Gravity Forms pushed generate_lead a second, independent way. CUFT_Gravity_Forms::track_submission() (PHP, hooked to gform_after_submission) also generated an inline generate_lead dataLayer push, on top of the client-side push every framework already fires via cuft-dataLayer-utils.js. For Elementor Pro, CF7 and Ninja Forms the equivalent server-side inline script never reaches the browser (their forms submit by AJAX, so the hook fires on a request whose response carries no enqueued script tag), but Gravity Forms can complete the same request as a full page render, in which case the inline script did print and run. Removed the server-side generate_lead_event() push; Gravity's server-side form_submit push is unaffected.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.28.1.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.28.0

Choose a tag to compare

@github-actions github-actions released this 23 Sep 22:54

WordPress.org directory review preparation. Plugin Check (general, plugin_repo, security, performance, accessibility) on the directory package went from 635 warnings to none.

Changed

  • generate_lead obeys the Generate Lead Events setting. cuft-dataLayer-utils.js pushed it on every submission with an email address whatever cuft_generate_lead_enabled said. The setting now reaches the browser as window.cuftLeadSettings (inline before cuft-dataLayer-utils), and gates the push, the deprecated strict dual-fire, and the recordEvent call; cuft_record_event also refuses generate_lead while the setting is off. A page cached before the upgrade, with no cuftLeadSettings, keeps pushing as it did.
  • Upgrade defaults (migration 3.28.0, cuft_db_version now 3.28.0). An existing install gets cuft_generate_lead_enabled = 1 and cuft_webhook_require_key = 0, so it behaves as before; a new install gets cuft_generate_lead_enabled = 0 and cuft_webhook_require_key = 1 with a generated key. Existing means: a stored cuft_db_version; or, during activation, cuft_db_version or cuft_gtm_id present before activation wrote its defaults; or, outside activation, cuft_gtm_id present.
  • Requires WordPress 6.2. Every query goes through $wpdb->prepare() with identifiers bound by %i.
  • Main class renamed Choice_Universal_Form_Tracker to CUFT_Plugin, and Abstract_CUFT_Adapter to CUFT_Abstract_Adapter, for the directory's prefix rule.
  • Plugin URI now points at https://choice.marketing/tools/choice-uft/; License header reads GPLv2 or later.
  • Admin notices show only on the plugin's screens and the Plugins screen; the informational notice is dismissible per user.
  • Inline admin scripts and styles moved to assets/cuft-admin.js and assets/cuft-admin.css; the GTM loader is enqueued through wp_add_inline_script.
  • error_log() calls route through CUFT_Logger::debug_log(), which writes only under WP_DEBUG.
  • readme == External services == rewritten per service, with terms and privacy links for the Choice OMG phone validation service and its sub-processors.

Security

  • Webhook key. New setting "Require webhook key" on the Click Tracking screen. When on, cuft_webhook rejects any request whose key does not match cuft_webhook_key (hash_equals), with HTTP 403. The screen shows the full keyed URL and a nonce-checked Regenerate button; the "security through obscurity" wording is gone.
  • Test mode, which let any visitor append ?test_mode=1 and suppress form notification emails, is removed with the Test Form Builder.
  • Nonce plus manage_options on every admin action and AJAX handler; request, cookie and server input unslashed and sanitized.
  • CSV exports neutralise spreadsheet formulas in visitor-supplied columns.

Removed

  • Test Form Builder. Framework adapters, adapter factory, form builder and its AJAX endpoints, the test form templates, sessions and validator, test mode (?test_mode=1), the /cuft-test-form/ rewrite rule and the form_id/test_mode query vars, and their Testing Dashboard card and scripts. The sample data generator, event simulator and test events table remain. Test forms made by earlier versions are deleted on upgrade and uninstall by CUFT_Legacy_Test_Forms, which matches only posts carrying _cuft_test_form = 1 and a cuft_test_<time>_<4 digits> instance ID, never titles.
  • The Gravity Forms and Ninja Forms scripts' test-mode blocks, which wrote fake gclid and UTM values into session storage on any URL containing test=1, cuft_test=1 or -test-form.

Fixed

  • Stray translator comment printed on the Testing Dashboard.
  • Migration index check looked for date_updated while creating idx_date_updated, so a second run failed.
  • CUFT_Logger::log() was called with its arguments reversed in the click tracker and event recorder.
  • Uninstall cleared a cron hook the plugin never scheduled and left the real ones, the test events table, and transient timeouts behind.
  • The directory package hides the GTM template download buttons, whose files it does not ship.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.28.0.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.27.1

Choose a tag to compare

@github-actions github-actions released this 19 Sep 03:48

Fixed

  • A cached page could silently cost a visitor their attribution. The cuft_store_utm call that writes the UTM and click-id cookies carries a nonce printed into the page HTML, and a page cache can serve that HTML for longer than WordPress keeps a nonce valid (24 hours). Past that point the call was rejected, no cookie was written, and every form submission from that page arrived with no attribution and no error recorded anywhere. The cookies are now written by the page itself, in the format the server already reads, so attribution survives a stale cached page. Verified against a page whose store call returns 403: the cookie is written and the webhook carries the full attribution set.

Security

  • The cuft_store_utm endpoint keeps its nonce check. Removing it would let any third-party site forge attribution cookies for a visitor through the endpoint, and the client-side write closes the cache gap without weakening that.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.27.1.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.27.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 03:41

Fixed

  • Attribution never reached the stored Elementor submission. add_attribution_to_record() was hooked on elementor_pro/forms/new_record, which Elementor Pro fires after every submit action has already run, including the one that saves the entry. The hidden fields it added therefore arrived too late for the Submissions module and for the webhook, and had never appeared on a stored entry since 3.24.0. Attribution is now captured on elementor_pro/forms/record/actions_before, which runs in the visitor's request before any action.
  • submitted_at reported the time a later action fired, not the submit time. Elementor runs actions in sequence in one request, so a slow action ahead of the webhook pushed the timestamp out by however long it took; on one site a mail service added a steady 11 seconds. The payload is now assembled once per submission and reused, so submitted_at, the stored entry and the webhook all carry the same submit time.

Added

  • tests/unit/test-elementor-attribution-capture.php: drives Elementor's real hook sequence (record/actions_before, the actions, new_record) and covers the capture, the reuse of the captured payload when the cookies are gone, timestamp stability, the live-request fallback, and the field cleanup described below.

Security

  • Attribution added to the record for the Submissions module is removed again as soon as the entry is stored, so it cannot reach a notification email. Elementor's [all-fields] shortcode prints every field on the record with no filtering. The injection is also skipped entirely unless the form stores submissions and the Submissions action is ordered ahead of both email actions, verified per submission rather than assumed.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.27.0.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.26.1

Choose a tag to compare

@github-actions github-actions released this 07 Sep 22:49

Fixed

  • Avada forms pushed form_submit on pages nobody submitted. isAvadaSuccessState() tested element.style.display !== "none", which reads only the inline style attribute. Avada renders .fusion-form-response-success into the static markup and hides it with a CSS class carrying no style attribute, so the value was "", the check passed about 50 ms after the page loaded, and every render that started an observation produced a submission event. Measured on one client: 29 contact-less rows against 32 real leads in a month, driven by OEM lock-screen ad renderers (com.samsung.android.dynamiclock, com.hihonor.magazine, com.heytap.pictorial) and Meta's ad crawler re-rendering the landing page, each phantom carrying the ad's gclid. Visibility is now read from computed style plus layout, and every success signal must transition from absent to present after the submission, so a message that was already on the page cannot register as one.
  • Avada submissions with neither an email address nor a phone number no longer push form_submit. The framework module already requires an email field before it tracks a form at all, so a submission reaching this state is a false positive, not a name-only form.
  • The Avada click watcher listened on .fusion-button, which on a typical Avada page matches dozens of ordinary buttons (32 on the client contact page). It now binds to real submit controls only.

Added

  • tests/standalone/test-avada-success-detection.html: browser regression cover for the above. A hidden-by-class success node must not fire, a success node becoming visible must fire, a contact-less success must be suppressed, and a non-submit Fusion button must start no observation.

Changed

  • trackFormSubmission() accepts an opt-in require_contact option that suppresses a form_submit carrying neither email nor phone. Only the Avada module sets it. It is off by default because the dataLayer specification requires form_submit on every real submission, name-only and multi-step forms included, so the other frameworks are untouched.

Removed

  • Auto-BCC email system (Feature 010): Deleted the 11 files it left behind. The feature was dropped from the plugin loader in 3.22.0, which orphaned includes/email/, the AJAX handler, the settings view, and its CSS and JS in the tree. Nothing required them and there is no autoloader, so those classes were never defined, the class_exists() guards in choice-universal-form-tracker.php never fired, and the code could not run on any install. The dead init block went with it. uninstall.php already sweeps cuft_% options and transients, so a cuft_auto_bcc_config row left by a pre-3.22.0 install is still cleaned up on uninstall.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.26.1.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.26.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 05:23

Added

  • WordPress.org distribution: build.sh now produces two packages from one tree. ./build.sh builds the GitHub release exactly as before; ./build.sh --wporg builds the directory package, which omits the self-update subsystem because directory guideline 8 forbids a hosted plugin from serving its own updates. The extra exclusions live in .wporgignore.
  • Choice_Universal_Form_Tracker::has_updater() reports whether the update subsystem is bundled. Loading, the Force Update tab, its assets, and the custom update notice all check it, so the directory build degrades to core-managed updates instead of failing.
  • build.sh fails the build on a version mismatch across the three version sources, on hidden files, on em-dashes in shipped files, and (for --wporg) on any self-update file or third-party CDN reference reaching the package.
  • readme.txt gained an == External services == section itemising every third-party endpoint the plugin can contact, what is transmitted, when, and under which terms and privacy policy.

Changed

  • SHA-256 now ships with the plugin. CryptoJS was loaded from cdnjs.cloudflare.com, which guideline 8 prohibits and which introduced a race: a slow CDN response left lead_id off the event entirely. The bundled implementation is synchronous, so lead_id is always available when the payload is built. Digests are unchanged and still match PHP hash('sha256').
  • Declared PHP 7.4 consistently. The plugin header had no Requires PHP at all, readme.txt claimed 7.4, and the runtime notice named 7.0.
  • Added Plugin URI, Requires at least, Requires PHP, and License URI to the plugin header.
  • Text domain corrected to choice-universal-form-tracker in 275 places that used choice-uft, which did not match the declared Text Domain header and left those strings untranslatable.
  • readme.txt tags reduced from 12 to the 5 WordPress.org permits, and the six that were other companies' trademarks removed. Framework compatibility is described in the Description body instead.

Fixed

  • GitHub updates were silently switched off on every settings save. The checkbox that save_settings() read was removed in Feature 008, so the value was always false and got written back over the stored option.
  • save_settings() now verifies the settings nonce itself rather than relying on its caller, and unslashes $_POST values before sanitising them.
  • Escaped previously unescaped output across the admin screens, the GTM injector, and the testing dashboard.
  • Replaced date() with gmdate(), rand() with wp_rand(), and parse_url() with wp_parse_url().
  • /llms.txt, /ai.txt, and /llms-full.txt now send X-Content-Type-Options: nosniff.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.26.0.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues

Version 3.25.0

Choose a tag to compare

@peterjaffray peterjaffray released this 30 Jun 21:43

Added

  • Shared cross-system lead ID (OPS-2210): every lead now carries one deterministic lead_id across the webhook payload, the stored entry, the email notification, and the analytics event, so a booked client in the CRM can be matched back to the campaign that produced them.
    • lead_id is the lowercase sha256 hex of the normalized email (trim + lowercase). This matches Google Enhanced Conversions and Meta CAPI email normalization, so the same hash doubles as a cross-platform match key.
    • Phone fallback: when a submission has no email, lead_id is the sha256 of the phone normalized to E.164 (+<digits>, NANP default, filterable via cuft_lead_id_phone_country).
    • New lead_id_source field records the basis (email or phone) so downstream consumers know which value was hashed. Both fields are omitted when neither email nor phone is present.
    • New CUFT_Form_Attribution::lead_id_from_email() and lead_id_from_phone() helpers; final value filterable via cuft_lead_id.
    • Added to the Elementor server-side form_submit and generate_lead dataLayer pushes, and computed client-side via CryptoJS for the analytics event (PHP and JS produce identical digests).

Why

Closes OPS-2210 and unblocks downstream offline-conversion import (OPS-2211) and lead reporting (OPS-2212): a deterministic hash lets WordPress, n8n, Cliniko, QuickBooks, and the ad platforms all derive the same identifier from the same person without passing a generated UUID around, while keeping raw PII out of analytics.

Installation

Automatic Update

If you already have the plugin installed, it should automatically detect this update. Go to Plugins in your WordPress admin and click Update Now.

Manual Installation

  1. Download the choice-uft-v3.25.0.zip file from the assets below
  2. In WordPress admin, go to Plugins → Add New → Upload Plugin
  3. Choose the downloaded zip file and click Install Now
  4. Activate the plugin after installation

Requirements

  • WordPress 5.0 or higher
  • PHP 7.4 or higher

Support

For issues or questions, please visit: https://github.com/ChoiceOMG/choice-uft/issues