# BW Lead Attribution Intelligence

Capture traffic source, attribute it to every lead, and understand where your leads are coming from.

## Requirements

- WordPress 6.0 or later
- PHP 7.4 or later
- Gravity Forms (optional — the merge tags and the BW Lead Data field need it; capture works without it).
  Tested against **Gravity Forms 2.10.x and 3.0.1**. Note that Gravity Forms 3.0 itself
  requires WordPress 6.5 or later.

## Installation

1. Download the latest release zip from https://plugins.bowden.works/bw-lead-ai/
2. In WP Admin, go to Plugins → Add New → Upload Plugin.
3. Upload the zip and activate.

Once installed, BW Lead Attribution Intelligence will automatically check for updates from `plugins.bowden.works`.

## Features

- **Traffic source capture** that survives the visitor wandering your site. UTMs only
  exist on the landing URL, so the plugin stores the visit in the browser and attaches it
  to whatever form they eventually submit — even days later, across sessions.
- **A strict resolution cascade**: explicit UTMs → click IDs (`gclid`, `fbclid`, `msclkid`,
  `gbraid`, …) → referrer classification → direct. Every step is configurable.
- **Channel mapping** — turn `google / cpc` into "Google Ads" with your own rules.
- **Custom dimensions** — track any URL parameter you like (e.g. match type) and use it
  as a merge tag, no code required.
- **A native Gravity Forms field** ("BW Lead Data") — drop it on a form and pick what it
  captures from one dropdown.
- **Interaction tracking** (all opt-in, off by default) — video plays and progress
  milestones, file downloads, phone and email clicks, social and outbound links, custom
  CSS-selector actions, scroll depth, and form starts.
- **A UTM builder** and a **Test tab** with simulated test links covering every traffic
  type, showing what each one resolves to against your own settings.

Everything runs in the visitor's browser. No server-side per-visit record, no custom
database tables, no IP logging.

## Merge tags

| Tag | Value |
|---|---|
| `{bw:source}` `{bw:medium}` `{bw:source_medium}` | Latest visit's traffic source |
| `{bw:channel}` | Mapped channel label, e.g. "Google Ads" |
| `{bw:campaign}` `{bw:term}` `{bw:content}` `{bw:adgroup}` | Campaign dimensions |
| `{bw:first_source}` `{bw:first_medium}` `{bw:first_channel}` `{bw:first_page}` | First touch |
| `{bw:last_page}` `{bw:submit_page}` | Landing page, and the page the form was submitted on |
| `{bw:visits}` `{bw:pages}` `{bw:tagged_visits}` | Counters |
| `{bw:summary}` `{bw:summary_detailed}` | Full attribution readout |
| `{bw:events}` `{bw:events_list}` `{bw:event.<type>}` | Interactions (when enabled) |
| `{bw:<your_custom_dimension>}` | Anything you define in Parameter Aliases |

## For theme and plugin developers

**BW Lead Data fields are populated in the browser, not on the server.** That has one
consequence worth knowing if you write code that walks a Gravity Form:

> A BW Lead Data field is `gfield_visibility_visible` in Gravity Forms terms.
> **It must be** — a field the browser fills has to actually render on the front end.
> Its wrapper carries the `gform_hidden` class instead.

So if your code decides "is this field user-facing?" — for a review step, a confirmation
summary, a progress indicator, an accessibility pass — **key on `gform_hidden`**, not on
`gfield_visibility_hidden` and not on an inline `display: none` (which only appears for
Gravity Forms conditional logic). Miss that and you may display a visitor's attribution
data back to them.

```js
// Treat these as not user-facing:
form.querySelectorAll( '.gfield:not(.gform_hidden)' ) // ...is what you want to walk
```

Every data point renders as `<input type="hidden">`, including the multi-line Summary
ones, so filtering by input type works too. Do not rely on that alone, though — the
wrapper class is the contract.

**One sharp edge:** when Settings → Debug is on, an admin sees these fields rendered
visibly and the wrapper carries `bw-lead-ai-debug-visible` instead of `gform_hidden`.
That is deliberate — in debug mode the field *is* meant to be seen — but it means
class-based checks give a different answer for admins while debug is enabled.

## FAQ

**Q: Where do updates come from?**
A: Direct from Bowden Works at `plugins.bowden.works`. No WP.org involvement.

**Q: Does this work without Gravity Forms?**
A: Capture does. To get values into a non-GF form, name the field each data point should
fill under Settings → Form Field Mapping.

**Q: Does it store personal data or set tracking cookies?**
A: It stores traffic-source data in the visitor's own `localStorage` (falling back to a
cookie only when `localStorage` is unavailable). No IP addresses are logged and nothing is
sent to a server until the visitor submits a form.

**Q: How do I report a bug?**
A: See the SECURITY.md and CONTRIBUTING.md in this plugin's repository.

## License

GPL-2.0-or-later. See `LICENSE`.
